/* 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 #include #include #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; }