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.
374 lines
9.8 KiB
374 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;
|
|
}
|