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