Skip to content

Instantly share code, notes, and snippets.

@vodik
Created November 16, 2011 05:40
Show Gist options
  • Save vodik/1369374 to your computer and use it in GitHub Desktop.
Save vodik/1369374 to your computer and use it in GitHub Desktop.
Template for a WAVE reader.
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) Simon Gomizelj, 2011
*/
#include "alsa.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <alsa/asoundlib.h>
#include "wavereader.h"
#include "format.h"
struct snd_out {
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
};
void
playwave(snd_out_t *out, wave_t *wave, format_info_t *info)
{
int rc;
snd_pcm_uframes_t frames;
long loops;
unsigned val;
int dir, size;
char *buffer;
snd_pcm_hw_params_get_period_size(out->params, &frames, &dir);
size = frames * info->bytes_sample * info->channels;
buffer = malloc(size);
snd_pcm_hw_params_get_period_time(out->params, &val, &dir);
//loops = 15000000 / val; /* 5 seconds / period time */
//while (loops > 0) {
while (true) {
--loops;
/* rc = read(0, buffer, size); */
rc = waveread(wave, buffer, size);
if (rc == 0) {
fprintf(stderr, "end of file on input\n");
break;
} else if (rc < 0) {
fprintf(stderr, "something went wrong, bailing\n");
break;
} else if (rc != size)
fprintf(stderr, "short read (%d bytes)\n", rc);
rc = snd_pcm_writei(out->handle, buffer, frames);
if (rc == -EPIPE) {
fprintf(stderr, "underrun occurred\n");
snd_pcm_prepare(out->handle);
} else if (rc < 0) {
perror("snd_pcm_writei");
exit(EXIT_FAILURE);
} else if (rc != (int)frames)
fprintf(stderr, "shord write, write %d frames\n", rc);
}
snd_pcm_drain(out->handle);
free(buffer);
}
snd_out_t *
sndopen(format_info_t *info)
{
unsigned val = info->rate;
int dir;
snd_out_t *out = malloc(sizeof(snd_out_t));
if (snd_pcm_open(&out->handle, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) {
perror("snd_pcm_open");
exit(EXIT_FAILURE);
}
snd_pcm_hw_params_alloca(&out->params);
snd_pcm_hw_params_any(out->handle, out->params);
snd_pcm_hw_params_set_access(out->handle, out->params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(out->handle, out->params, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(out->handle, out->params, info->channels);
snd_pcm_hw_params_set_rate_near(out->handle, out->params, &val, &dir);
printf(" ALSA using rate %u\n", val);
if (snd_pcm_hw_params(out->handle, out->params) < 0) {
perror("snd_pcm_hw_params");
exit(EXIT_FAILURE);
}
return out;
}
void
sndclose(snd_out_t *out)
{
snd_pcm_close(out->handle);
free(out);
}
#ifndef ALSA_H
#define ALSA_H
#include "format.h"
#include "wavereader.h"
struct snd_out;
typedef struct snd_out snd_out_t;
snd_out_t *sndopen(format_info_t *info);
void sndclose(snd_out_t *out);
void playwave(snd_out_t *out, wave_t *wave, format_info_t *info);
#endif
#ifndef FORMAT_H
#define FORMAT_H
struct format_info;
typedef struct format_info format_info_t;
struct format_info {
int channels;
int rate;
int bytes_sample;
};
#endif
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) Simon Gomizelj, 2011
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include "alsa.h"
#include "format.h"
#include "wavereader.h"
void
print_waveinfo(wave_t *wave)
{
int32_t size, sample_rate, bytes_per_second, length;
int16_t pcm, channels, block_align, bits_per_second;
/* read properties so we can dump wave file information */
wavegetprop(wave, WAVE_PCM, &pcm);
wavegetprop(wave, WAVE_CHANNELS, &channels);
wavegetprop(wave, WAVE_SIZE, &size);
wavegetprop(wave, WAVE_SAMPLE_RATE, &sample_rate);
wavegetprop(wave, WAVE_BYTES_PER_SEC, &bytes_per_second);
wavegetprop(wave, WAVE_BLOCK_ALIGN, &block_align);
wavegetprop(wave, WAVE_BITS_PER_SAMPLE, &bits_per_second);
wavegetprop(wave, WAVE_LENGTH, &length);
printf("size: %d bytes\n", size);
printf("channels: %d bit", channels);
if (channels == 1)
printf(" (mono)\n");
else if (channels == 2)
printf(" (stereo)\n");
else
printf("\n");
if (pcm == 1) {
int hours, minuites, seconds = length;
minuites = seconds / 60;
hours = minuites / 60;
seconds %= 60;
minuites %= 60;
printf("uncompressed wave file:\n");
printf(" > length (approx): %02dh %02dm %02ds\n", hours, minuites, seconds);
} else
printf("compressed wave file (code %d)\n", pcm);
printf("sample rate: %d Hz\n", sample_rate);
printf("average bytes/second %d\n", bytes_per_second);
printf("block align: %d\n", block_align);
printf("bits/sample: %d\n", bits_per_second);
}
int
main(int argc, char *argv[])
{
FILE *fd;
wave_t *wave;
snd_out_t *out;
format_info_t info;
if (argc < 2) {
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
exit(EXIT_FAILURE);
}
if (!(fd = fopen(argv[1], "rb"))) {
fprintf(stderr, "Couldn't open \"%s\".\n", argv[1]);
exit(EXIT_FAILURE);
}
if (!(wave = waveopen(fd))) {
fprintf(stderr, "Couldn't open \"%s\" as a .wav file.\n", argv[1]);
exit(EXIT_FAILURE);
}
print_waveinfo(wave);
wavegetformat(wave, &info);
if (!(out = sndopen(&info))) {
fprintf(stderr, "Couldn't open output device\n");
exit(EXIT_FAILURE);
}
playwave(out, wave, &info);
sndclose(out);
waveclose(wave);
fclose(fd);
return 0;
}
CC = gcc
CFLAGS = -Wall -pedantic -std=gnu99 -g
LDFLAGS = -lasound
OBJ = wavereader.o alsa.o main.o
all: ademo
ademo: ${OBJ}
@echo CC -o $@
@${CC} -o $@ ${OBJ} ${LDFLAGS}
%.o: %.c
@echo CC $@
@${CC} -o $@ -c ${CFLAGS} $<
clean:
@echo cleaning up...
@rm *.o ademo
.PHONY: all clean
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) Simon Gomizelj, 2011
*/
#include "wavereader.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include "format.h"
#define RIFF_MAGIC ('R' | 'I' << 8 | 'F' << 16 | 'F' << 24)
#define WAVE_MAGIC ('W' | 'A' << 8 | 'V' << 16 | 'E' << 24)
/* supported chunk types */
#define FMT_CHUNK ('f' | 'm' << 8 | 't' << 16 | ' ' << 24)
#define DATA_CHUNK ('d' | 'a' << 8 | 't' << 16 | 'a' << 24)
#define LIST_CHUNK ('L' | 'I' << 8 | 'S' << 16 | 'T' << 24)
/* chunk types what I do not support (but will check for) */
#define FACT_CHUNK ('f' | 'a' << 8 | 'c' << 16 | 't' << 24)
#define WAVL_CHUNK ('w' | 'a' << 8 | 'v' << 16 | 'l' << 24)
#define SLNT_CHUNK ('s' | 'l' << 8 | 'n' << 16 | 't' << 24)
#define CUE_CHUNK ('c' | 'u' << 8 | 'e' << 16 | ' ' << 24)
#define PLST_CHUNK ('p' | 'l' << 8 | 's' << 16 | 't' << 24)
#define LABL_CHUNK ('l' | 'a' << 8 | 'b' << 16 | 'l' << 24)
#define NOTE_CHUNK ('n' | 'o' << 8 | 't' << 16 | 'e' << 24)
#define LTXT_CHUNK ('l' | 't' << 8 | 'x' << 16 | 't' << 24)
#define SMPL_CHUNK ('s' | 'm' << 8 | 'p' << 16 | 'l' << 24)
#define INST_CHUNK ('i' | 'n' << 8 | 's' << 16 | 't' << 24)
struct wave_chunk {
int32_t id;
int32_t length;
} __attribute__ ((__packed__));
struct wave_fmt_chunk {
struct wave_chunk h;
int16_t compression;
int16_t channels;
int32_t sample_rate;
int32_t bytes_per_second;
int16_t block_align;
int16_t bits_per_second;
char fmt_extra[];
} __attribute__ ((__packed__));
struct wave_data_chunk {
struct wave_chunk h;
char data[];
} __attribute__ ((__packed__));
struct wave_fact_chunk {
struct wave_chunk h;
int32_t samples;
char reserved[];
} __attribute__ ((__packed__));
struct wave_toc {
int32_t id;
int32_t length;
long offset;
struct wave_toc *next;
};
struct wave {
FILE *fp;
int32_t length;
struct wave_toc *toc;
struct wave_fmt_chunk *fmt;
struct wave_data_chunk *data;
struct wave_fact_chunk *fact;
/* TODO: this is very simplistic, this will need to be cleaned up
* if we want to load the wave file on demand */
char *data_ptr;
size_t remaining;
};
static void
dump_int32(int32_t i)
{
printf("%c", i & 0xff);
i >>= 8;
printf("%c", i & 0xff);
i >>= 8;
printf("%c", i & 0xff);
i >>= 8;
printf("%c", i & 0xff);
}
static void
dump_toc(wave_t *wave)
{
struct wave_toc *entry;
printf(">> DUMPING TOC\n");
for (entry = wave->toc; entry; entry = entry->next) {
switch (entry->id) {
case FMT_CHUNK:
printf(" -> fmt chunk\n");
break;
case DATA_CHUNK:
printf(" -> data chunk\n");
break;
case FACT_CHUNK:
printf(" -> fact chunk\n");
break;
case LIST_CHUNK:
printf(" -> LIST chunk\n");
break;
default:
printf(" -> unsupported chunk found: ");
dump_int32(entry->id);
printf("\n");
break;
}
}
}
static void
dump_fmt(struct wave_fmt_chunk *fmt)
{
printf(">> fmt chunk found (%d bytes long)\n", fmt->h.length);
if (fmt->h.length > 16) {
printf(">> DEBUG: printing fmt_extra\n");
int i;
for (i = 0; i < fmt->h.length - 16; i += 2)
printf(" %02hhx %02hhx\n", fmt->fmt_extra[i] & 0xff, fmt->fmt_extra[i + 1] & 0xff);
}
}
static void *
get_chunk(wave_t *wave, int id)
{
void *chunk;
size_t chunk_size;
struct wave_toc *entry;
for (entry = wave->toc; entry && entry->id != id; entry = entry->next);
if (!entry)
return NULL;
chunk_size = sizeof(struct wave_chunk) + entry->length;
chunk = malloc(chunk_size);
fseek(wave->fp, entry->offset, SEEK_SET);
fread(chunk, chunk_size, 1, wave->fp);
return chunk;
}
static void
build_toc(wave_t *wave)
{
struct wave_toc *entry;
struct wave_chunk chunk;
int offset;
const size_t chunk_size = sizeof(struct wave_chunk);
while (1) {
offset = ftell(wave->fp);
if (!fread(&chunk, chunk_size, 1, wave->fp))
break;
if (!wave->toc)
entry = wave->toc = malloc(sizeof(struct wave_toc));
else
entry = entry->next = malloc(sizeof(struct wave_toc));
entry->id = chunk.id;
entry->length = chunk.length;
entry->offset = offset;
entry->next = NULL;
/* Lets be oppertunistic here and read off the
* fmt chunk the second we find it */
if (chunk.id == FMT_CHUNK) {
wave->fmt = malloc(chunk_size + chunk.length);
wave->fmt->h = chunk;
fread((char *)wave->fmt + chunk_size, entry->length, 1, wave->fp);
} else
fseek(wave->fp, chunk.length, SEEK_CUR);
}
}
wave_t *
waveopen(FILE *fp)
{
wave_t *wave = NULL;
int32_t header[3];
/* check for magic numbers */
fread(header, sizeof(int32_t), 3, fp);
if (header[0] != RIFF_MAGIC || header[2] != WAVE_MAGIC)
return NULL;
/* header is okay, allocate the wave_t struct */
if (!(wave = malloc(sizeof(wave_t)))) {
perror("malloc");
exit(EXIT_FAILURE);
}
memset(wave, 0, sizeof(wave_t));
wave->fp = fp;
wave->length = header[1];
build_toc(wave);
dump_toc(wave);
assert(wave->fmt->h.length >= 16);
dump_fmt(wave->fmt);
wave->fact = get_chunk(wave, FACT_CHUNK);
if (wave->fact) {
printf(">> DEBUG: fact frame %d bytes\n", wave->fact->h.length);
printf(" samples: %d\n", wave->fact->samples);
}
/* TODO: read on demand: the whole data chunk is being read at
* once right now. Slooooooooow! */
wave->data = get_chunk(wave, DATA_CHUNK);
printf(">> DEBUG: data frame %d bytes\n", wave->data->h.length);
wave->data_ptr = wave->data->data;
wave->remaining = wave->data->h.length;
return wave;
}
int
waveclose(wave_t *wave)
{
assert(wave);
struct wave_toc *temp, *entry = wave->toc;
while (entry) {
temp = entry;
entry = entry->next;
free(temp);
}
free(wave->fmt);
if (wave->data)
free(wave->data);
if (wave->fact)
free(wave->fact);
free(wave);
return 0;
}
int
waveread(wave_t *wave, char *buf, size_t len)
{
if (wave->fmt->compression != 1) {
fprintf(stderr, "Only uncompressed wave files supported\n");
return -1;
}
size_t to_cpy = len > wave->remaining ? wave->remaining : len;
/* printf("TOCPYT: %lu\n", to_cpy); */
if (to_cpy) {
memcpy(buf, wave->data_ptr, len);
wave->remaining -= to_cpy;
wave->data_ptr += to_cpy;
}
return to_cpy;
}
int
wavegetprop(wave_t *wave, wave_prop_t prop, void *data)
{
switch (prop) {
case WAVE_SIZE:
*(int32_t *)data = wave->length;
return 0;
case WAVE_PCM:
*(int16_t *)data = wave->fmt->compression;
return 0;
case WAVE_CHANNELS:
*(int16_t *)data = wave->fmt->channels;
return 0;
case WAVE_SAMPLE_RATE:
*(int32_t *)data = wave->fmt->sample_rate;
return 0;
case WAVE_BYTES_PER_SEC:
*(int32_t *)data = wave->fmt->bytes_per_second;
return 0;
case WAVE_BLOCK_ALIGN:
*(int16_t *)data = wave->fmt->block_align;
return 0;
case WAVE_BITS_PER_SAMPLE:
*(int16_t *)data = wave->fmt->bits_per_second;
return 0;
case WAVE_LENGTH:
if (!wave->fmt->bits_per_second)
*(int32_t *)data = 0;
else
*(int32_t *)data = wave->data->h.length / 8 / wave->fmt->bits_per_second / 1000;
return 0;
default:
return -1;
}
}
int
wavegetformat(wave_t *wave, format_info_t *info)
{
assert(wave);
assert(info);
info->channels = wave->fmt->channels;
info->rate = wave->fmt->sample_rate;
info->bytes_sample = wave->fmt->bits_per_second / 8;
return 0;
}
int
waveseek(wave_t *wave, long offset, int whence)
{
/* TODO */
return 0;
}
int
waveeof(wave_t *wave)
{
/* TODO */
return 0;
}
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) Simon Gomizelj, 2011
*/
#ifndef WAVEREADER_H
#define WAVEREADER_H
#include <stdio.h>
#include "format.h"
struct wave;
typedef struct wave wave_t;
typedef enum {
WAVE_SIZE,
WAVE_PCM,
WAVE_CHANNELS,
WAVE_SAMPLE_RATE,
WAVE_BYTES_PER_SEC,
WAVE_BLOCK_ALIGN,
WAVE_BITS_PER_SAMPLE,
WAVE_LENGTH,
} wave_prop_t;
wave_t *waveopen(FILE *file);
int waveclose(wave_t *wave);
int waveread(wave_t *wave, char *buf, size_t len);
int wavegetprop(wave_t *wave, wave_prop_t prop, void *data);
int wavegetformat(wave_t *wave, format_info_t *info);
int waveseek(wave_t *wave, long offset, int whence);
int waveeof(wave_t *wave);
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment