Recently I'm doing a project about audio. The first problem I met is getting the WAV into memory. If this is not done, even the best theory cannot come true. So let's come to this problem.
Objectives:
Read a WAV file into C++ code.
Prerequisite:
Understanding pointer and memory organization in C++.
Why WAV? Actually, I want to talk about getting any type of files into my codes. Because any type of files is stored the same way. When you open a file with a binary viewer, you see something like "24 17 1e f3 3c 13 3c 14 16 f9 18 f9 34 e7 23 a6 3c f2 24 f2 11 ce 1a 0d" in hexadecimal notation. What I want to say is, we don't have to differ one type from another type and just read these bytes one by one into memory and that's all.
To work with files, we include fstream first. A file is like another area of memory. We can employ a pointer to manipulate it.
FILE* fp = fopen(fname,"rb");
fname is a string in the form of "filepath/filename.extension", and "rb" means "read only". If the file doesn't exist, fp is NULL.
Then we use fread function to read contents into memory and move the pointer in the meantime. For example, we we want to read 4 short type values into memory.
short arr_short[4]; fread(arr_short, sizeof(short), 4, fp);
Also, we can read 1 long type value into memory.
long num_long; fread(&num_long, sizeof(long), 1, fp);
Please note that the first argument is an address. Another thing, the pointer moves automatically. When we first read 4 short type values, the pointer move forward a distance of 4 shorts.
After we read all that we want, we should turn off the pointer to make the process more stable.
fclose(fp);
I guess you can handle a file now. Then we go on to WAV file. Different files have different organizations. This is the organization of WAV file. (I copy this from Stanford)
We see that in the beginning of the file the bytes have specific meanings. Now I don't want to talk about it anymore. See the codes below.
The beginning part defines a class to be the entrance of the WAV data read from a file.
#include<fstream> #include<iostream> using namespace std; class WavData { public: short* data; unsigned long size; WavData() { data = NULL; size = 0; } };
Then comes the function processing the reading job.
void loadWaveFile(char *fname, WavData *ret) { FILE* fp = fopen(fname,"rb"); if (fp) { char id[5]; unsigned long size; short format_tag, channels, block_align, bits_per_sample; unsigned long format_length, sample_rate, avg_bytes_sec, data_size; fread(id, sizeof(char), 4, fp); id[4] = '\0'; if (!strcmp(id, "RIFF")) { fread(&size, sizeof(unsigned long), 1, fp); fread(id, sizeof(char), 4, fp); id[4] = '\0'; if (!strcmp(id,"WAVE")) { fread(id, sizeof(char), 4, fp); fread(&format_length, sizeof(unsigned long),1,fp); fread(&format_tag, sizeof(short), 1, fp); fread(&channels, sizeof(short),1,fp); fread(&sample_rate, sizeof(unsigned long), 1, fp); fread(&avg_bytes_sec, sizeof(unsigned long), 1, fp); fread(&block_align, sizeof(short), 1, fp); fread(&bits_per_sample, sizeof(short), 1, fp); fread(id, sizeof(char), 4, fp); fread(&data_size, sizeof(unsigned long), 1, fp); ret->size = data_size/sizeof(short); ret->data = (short*) malloc(data_size); fread(ret->data, sizeof(short), ret->size, fp); } else { cout<<"Error: RIFF file but not a wave file\n"; } } else { cout<<"Error: not a RIFF file\n"; } } fclose(fp); }
Finally the main function does the testing.
int main() { WavData song; loadWaveFile("audio.wav",&song); cout<<"there are "<<song.size/2<<" samples in this WAV file."<<endl; return 0; }
I know you have one more question: What if I don't need all these segments and just want the data? It's easy. Please refer to "fseek" and you will know how it can be done.
See you next time.