Created
November 16, 2011 05:40
-
-
Save vodik/1369374 to your computer and use it in GitHub Desktop.
Template for a WAVE reader.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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