You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
373 lines
9.8 KiB
373 lines
9.8 KiB
/* 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; |
|
}
|
|
|