/* Sonic library
Copyright 2010
Bill Cox
This file is part of the Sonic Library.
This file is licensed under the Apache 2.0 license.
*/
/*
This file supports read/write wave files.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "wave.h"
#define WAVE_BUF_LEN 4096
struct waveFileStruct {
int numChannels;
int sampleRate;
FILE *soundFile;
int bytesWritten; /* The number of bytes written so far, including header */
int failed;
int isInput;
};
/* Write a string to a file. */
static void writeBytes(
waveFile file,
void *bytes,
int length)
{
size_t bytesWritten;
if(file->failed) {
return;
}
bytesWritten = fwrite(bytes, sizeof(char), length, file->soundFile);
if(bytesWritten != length) {
fprintf(stderr, "Unable to write to output file");
file->failed = 1;
}
file->bytesWritten += bytesWritten;
}
/* Write a string to a file. */
static void writeString(
waveFile file,
char *string)
{
writeBytes(file, string, strlen(string));
}
/* Write an integer to a file in little endian order. */
static void writeInt(
waveFile file,
int value)
{
char bytes[4];
int i;
for(i = 0; i < 4; i++) {
bytes[i] = value;
value >>= 8;
}
writeBytes(file, bytes, 4);
}
/* Write a short integer to a file in little endian order. */
static void writeShort(
waveFile file,
short value)
{
char bytes[2];
int i;
for(i = 0; i < 2; i++) {
bytes[i] = value;
value >>= 8;
}
writeBytes(file, bytes, 2);
}
/* Read bytes from the input file. Return the number of bytes actually read. */
static int readBytes(
waveFile file,
void *bytes,
int length)
{
if(file->failed) {
return 0;
}
return fread(bytes, sizeof(char), length, file->soundFile);
}
/* Read an exact number of bytes from the input file. */
static void readExactBytes(
waveFile file,
void *bytes,
int length)
{
int numRead;
if(file->failed) {
return;
}
numRead = fread(bytes, sizeof(char), length, file->soundFile);
if(numRead != length) {
fprintf(stderr, "Failed to read requested bytes from input file\n");
file->failed = 1;
}
}
/* Read an integer from the input file */
static int readInt(
waveFile file)
{
unsigned char bytes[4];
int value = 0, i;
readExactBytes(file, bytes, 4);
for(i = 3; i >= 0; i--) {
value <<= 8;
value |= bytes[i];
}
return value;
}
/* Read a short from the input file */
static int readShort(
waveFile file)
{
unsigned char bytes[2];
int value = 0, i;
readExactBytes(file, bytes, 2);
for(i = 1; i >= 0; i--) {
value <<= 8;
value |= bytes[i];
}
return value;
}
/* Read a string from the input and compare it to an expected string. */
static void expectString(
waveFile file,
char *expectedString)
{
char buf[11]; /* Be sure that we never call with a longer string */
int length = strlen(expectedString);
if(length > 10) {
fprintf(stderr, "Internal error: expected string too long\n");
file->failed = 1;
} else {
readExactBytes(file, buf, length);
buf[length] = '\0';
if(strcmp(expectedString, buf)) {
fprintf(stderr, "Unsupported wave file format\n");
file->failed = 1;
}
}
}
/* Write the header of the wave file. */
static void writeHeader(
waveFile file,
int sampleRate)
{
/* write the wav file per the wav file format */
writeString(file, "RIFF"); /* 00 - RIFF */
/* We have to fseek and overwrite this later when we close the file because */
/* we don't know how big it is until then. */
writeInt(file, 36 /* + dataLength */); /* 04 - how big is the rest of this file? */
writeString(file, "WAVE"); /* 08 - WAVE */
writeString(file, "fmt "); /* 12 - fmt */
writeInt(file, 16); /* 16 - size of this chunk */
writeShort(file, 1); /* 20 - what is the audio format? 1 for PCM = Pulse Code Modulation */
writeShort(file, 1); /* 22 - mono or stereo? 1 or 2? (or 5 or ???) */
writeInt(file, sampleRate); /* 24 - samples per second (numbers per second) */
writeInt(file, sampleRate * 2); /* 28 - bytes per second */
writeShort(file, 2); /* 32 - # of bytes in one sample, for all channels */
writeShort(file, 16); /* 34 - how many bits in a sample(number)? usually 16 or 24 */
writeString(file, "data"); /* 36 - data */
writeInt(file, 0); /* 40 - how big is this data chunk */
}
/* Read the header of the wave file. */
static int readHeader(
waveFile file)
{
int data;
expectString(file, "RIFF");
data = readInt(file); /* 04 - how big is the rest of this file? */
expectString(file, "WAVE"); /* 08 - WAVE */
expectString(file, "fmt "); /* 12 - fmt */
int chunkSize = readInt(file); /* 16 or 18 - size of this chunk */
if(chunkSize != 16 && chunkSize != 18) {
fprintf(stderr, "Only basic wave files are supported\n");
return 0;
}
data = readShort(file); /* 20 - what is the audio format? 1 for PCM = Pulse Code Modulation */
if(data != 1) {
fprintf(stderr, "Only PCM wave files are supported\n");
return 0;
}
file->numChannels = readShort(file); /* 22 - mono or stereo? 1 or 2? (or 5 or ???) */
file->sampleRate = readInt(file); /* 24 - samples per second (numbers per second) */
readInt(file); /* 28 - bytes per second */
readShort(file); /* 32 - # of bytes in one sample, for all channels */
data = readShort(file); /* 34 - how many bits in a sample(number)? usually 16 or 24 */
if(data != 16) {
fprintf(stderr, "Only 16 bit PCM wave files are supported\n");
return 0;
}
if (chunkSize == 18) { /* ffmpeg writes 18, and so has 2 extra bytes here */
data = readShort(file);
}
expectString(file, "data"); /* 36 - data */
readInt(file); /* 40 - how big is this data chunk */
return 1;
}
/* Close the input or output file and free the waveFile. */
static void closeFile(
waveFile file)
{
FILE *soundFile = file->soundFile;
if(soundFile != NULL) {
fclose(soundFile);
file->soundFile = NULL;
}
free(file);
}
/* Open a 16-bit little-endian wav file for reading. It may be mono or stereo. */
waveFile openInputWaveFile(
char *fileName,
int *sampleRate,
int *numChannels)
{
waveFile file;
FILE *soundFile = fopen(fileName, "rb");
if(soundFile == NULL) {
fprintf(stderr, "Unable to open wave file %s for reading\n", fileName);
return NULL;
}
file = (waveFile)calloc(1, sizeof(struct waveFileStruct));
file->soundFile = soundFile;
file->isInput = 1;
if(!readHeader(file)) {
closeFile(file);
return NULL;
}
*sampleRate = file->sampleRate;
*numChannels = file->numChannels;
return file;
}
/* Open a 16-bit little-endian wav file for writing. It may be mono or stereo. */
waveFile openOutputWaveFile(
char *fileName,
int sampleRate,
int numChannels)
{
waveFile file;
FILE *soundFile = fopen(fileName, "wb");
if(soundFile == NULL) {
fprintf(stderr, "Unable to open wave file %s for writing\n", fileName);
return NULL;
}
file = (waveFile)calloc(1, sizeof(struct waveFileStruct));
file->soundFile = soundFile;
file->sampleRate = sampleRate;
file->numChannels = numChannels;
writeHeader(file, sampleRate);
if(file->failed) {
closeFile(file);
return NULL;
}
return file;
}
/* Close the sound file. */
int closeWaveFile(
waveFile file)
{
FILE *soundFile = file->soundFile;
int passed = 1;
if(!file->isInput) {
if(fseek(soundFile, 4, SEEK_SET) != 0) {
fprintf(stderr, "Failed to seek on input file.\n");
passed = 0;
} else {
/* Now update the file to have the correct size. */
writeInt(file, file->bytesWritten - 8);
if(file->failed) {
fprintf(stderr, "Failed to write wave file size.\n");
passed = 0;
}
if(fseek(soundFile, 40, SEEK_SET) != 0) {
fprintf(stderr, "Failed to seek on input file.\n");
passed = 0;
} else {
/* Now update the file to have the correct size. */
writeInt(file, file->bytesWritten - 48);
if(file->failed) {
fprintf(stderr, "Failed to write wave file size.\n");
passed = 0;
}
}
}
}
closeFile(file);
return passed;
}
/* Read from the wave file. Return the number of samples read. */
int readFromWaveFile(
waveFile file,
short *buffer,
int maxSamples)
{
int i, bytesRead, samplesRead;
int bytePos = 0;
unsigned char bytes[WAVE_BUF_LEN];
short sample;
if(maxSamples*file->numChannels*2 > WAVE_BUF_LEN) {
maxSamples = WAVE_BUF_LEN/(file->numChannels*2);
}
bytesRead = readBytes(file, bytes, maxSamples*file->numChannels*2);
samplesRead = bytesRead/(file->numChannels*2);
for(i = 0; i < samplesRead*file->numChannels; i++) {
sample = bytes[bytePos++];
sample |= (unsigned int)bytes[bytePos++] << 8;
*buffer++ = sample;
}
return samplesRead;
}
/* Write to the wave file. */
int writeToWaveFile(
waveFile file,
short *buffer,
int numSamples)
{
int i;
int bytePos = 0;
unsigned char bytes[WAVE_BUF_LEN];
short sample;
int total = numSamples*file->numChannels;
for(i = 0; i < total; i++) {
if(bytePos == WAVE_BUF_LEN) {
writeBytes(file, bytes, bytePos);
bytePos = 0;
}
sample = buffer[i];
bytes[bytePos++] = sample;
bytes[bytePos++] = sample >> 8;
}
if(bytePos != 0) {
writeBytes(file, bytes, bytePos);
}
return file->failed;
}