blob: c7d5c1add58e9769403105ce6c505191bd55e5c3 [file] [log] [blame]
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
struct wav_header {
char riff[4];
uint32_t chunk_size;
char format[4];
char subchunk1_id[4];
uint32_t subchunk1_size;
uint16_t audio_format;
uint16_t num_channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bits_per_sample;
char subchunk2_id[4];
uint32_t subchunk2_size;
} __attribute__((packed));
static void init_wav_header(struct wav_header *hdr,
uint32_t num_samples,
uint16_t bits_per_sample,
int channels,
uint32_t sample_rate)
{
hdr->riff[0] = 'R';
hdr->riff[1] = 'I';
hdr->riff[2] = 'F';
hdr->riff[3] = 'F';
hdr->subchunk2_size = num_samples * channels * bits_per_sample / 8;
hdr->chunk_size = 36 + hdr->subchunk2_size;
hdr->format[0] = 'W';
hdr->format[1] = 'A';
hdr->format[2] = 'V';
hdr->format[3] = 'E';
hdr->subchunk1_id[0] = 'f';
hdr->subchunk1_id[1] = 'm';
hdr->subchunk1_id[2] = 't';
hdr->subchunk1_id[3] = ' ';
hdr->subchunk1_size = 16;
hdr->audio_format = 1; /* PCM */
hdr->num_channels = channels;
hdr->sample_rate = sample_rate;
hdr->byte_rate = sample_rate * channels * bits_per_sample / 8;
hdr->block_align = channels * bits_per_sample / 8;
hdr->bits_per_sample = bits_per_sample;
hdr->subchunk2_id[0] = 'd';
hdr->subchunk2_id[1] = 'a';
hdr->subchunk2_id[2] = 't';
hdr->subchunk2_id[3] = 'a';
}
static const int divs_8000[] = { 5, 6, 6, 5 };
static const int divs_11025[] = { 4 };
static const int divs_22050[] = { 2 };
static const int divs_44100[] = { 1 };
int downsample(const int16_t *in, int16_t *out, int len, int *consumed,
const int *divs, int divs_len,
int out_stereo)
{
int i, j, lsum, rsum;
int di, div;
int oi;
i = 0;
oi = 0;
di = 0;
div = divs[0];
while (i + div * 2 <= len) {
// printf("div %d, i %d, oi %d\n", div, i, oi);
for (j = 0, lsum = 0, rsum = 0; j < div; j++) {
lsum += in[i + j * 2];
rsum += in[i + j * 2 + 1];
}
if (!out_stereo)
out[oi] = (lsum + rsum) / (div * 2);
else {
out[oi] = lsum / div;
out[oi + 1] = rsum / div;
}
oi += out_stereo + 1;
i += div * 2;
div = divs[++di % divs_len];
}
// printf("done: i %d, len %d, oi %d\n", i, len, oi);
*consumed = i;
return oi;
}
#define FAILIF(x, ...) do if (x) { \
fprintf(stderr, __VA_ARGS__); \
exit(EXIT_FAILURE); \
} while (0)
int main(int argc, char **argv)
{
int opt, ifd, ofd;
int new_rate = -1;
const int *divs;
int divs_len;
int consumed;
int channels = -1;
char *input = NULL;
char *output = NULL;
int nr, nr_out, nw;
int put_header = 0;
int total = 0;
const int bits_per_sample = 16;
struct wav_header src_hdr, dst_hdr;
int16_t buf[2048];
while ((opt = getopt(argc, argv, "o:s:c:w")) != -1) {
switch (opt) {
case 'o':
FAILIF(output != NULL, "Multiple output files not supported\n");
output = strdup(optarg);
break;
case 's':
new_rate = atoi(optarg);
break;
case 'c':
channels = atoi(optarg);
break;
case 'w':
put_header = 1;
break;
default: /* '?' */
fprintf(stderr, "usage: %s -o<outfile> -s<sampling> -c<channels>\n",
*argv);
fprintf(stderr, "usage: %s -o<outfile> -s<sampling> -c<channels>\n",
*argv);
exit(EXIT_FAILURE);
}
}
FAILIF(channels != 1 && channels != 2, "-c value must be 1 or 2\n");
switch(new_rate) {
case 8000:
divs = divs_8000;
divs_len = 4;
break;
case 11025:
divs = divs_11025;
divs_len = 1;
break;
case 22050:
divs = divs_22050;
divs_len = 1;
break;
case 44100:
divs = divs_44100;
divs_len = 1;
break;
default:
FAILIF(1, "rate %d is not supported\n", new_rate);
}
FAILIF(!output, "Expecting an output file name\n");
FAILIF(optind >= argc, "Expecting an input file name\n");
input = argv[optind];
printf("input file [%s]\n", input);
printf("output file [%s]\n", output);
printf("new rate: [%d]\n", new_rate);
ifd = open(input, O_RDONLY);
FAILIF(ifd < 0, "Could not open %s: %s\n", input, strerror(errno));
ofd = open(output, O_WRONLY | O_CREAT | O_TRUNC, 0666);
FAILIF(ofd < 0, "Could not open %s: %s\n", output, strerror(errno));
if (strstr(input, ".wav")) {
FAILIF(read(ifd, &src_hdr, sizeof(src_hdr)) != sizeof(src_hdr),
"Failed to read input WAV header: %s\n",
strerror(errno));
FAILIF(src_hdr.audio_format != 1,
"Expecting PCM encoding\n");
FAILIF(src_hdr.sample_rate != 44100,
"Expecting 44.kHz files\n");
FAILIF(src_hdr.num_channels != 2,
"Expecting 2-channel files\n");
FAILIF(src_hdr.bits_per_sample != bits_per_sample,
"Expecting 16-bit PCM files\n");
}
if (put_header)
FAILIF(lseek(ofd, sizeof(struct wav_header), SEEK_SET) < 0,
"seek error in %s: %s\n", output, strerror(errno));
consumed = 0;
while (1) {
nr = read(ifd, buf + consumed, sizeof(buf) - consumed);
FAILIF(nr < 0, "could not read from %s: %s\n", input, strerror(errno));
if (!nr) {
printf("done\n");
break;
}
nr += consumed;
// printf("resampling %d samples\n", nr / 2);
nr_out = downsample(buf, buf, nr / 2, &consumed, divs, divs_len, channels == 2);
consumed *= 2;
// printf("done: %d samples were generated (consumed %d out of %d bytes)\n", nr_out, consumed, nr);
if (consumed < nr) {
memcpy(buf, buf + consumed, nr - consumed);
consumed = nr - consumed;
// printf("copied %d bytes to front\n", consumed);
}
else consumed = 0;
nr_out *= 2;
nw = write(ofd, buf, nr_out);
FAILIF(nw < 0, "could not write to %s: %s\n", output, strerror(errno));
FAILIF(nw != nr_out, "mismatch, generated %d, wrote %d bytes\n", nr_out, nw);
total += nw;
}
if (put_header) {
printf("writing WAV header\n");
lseek(ofd, 0, SEEK_SET);
init_wav_header(&dst_hdr,
total * 8 / (channels * bits_per_sample),
bits_per_sample,
channels,
new_rate);
FAILIF(write(ofd, &dst_hdr, sizeof(dst_hdr)) != sizeof(dst_hdr),
"Could not write WAV header: %s\n", strerror(errno));
}
close(ifd);
close(ofd);
return 0;
}