Reputation: 7343
I plan to create a program that will visualize the audio waveform of a .wav file.
So far, I have started by properly reading the header part of the said wav file. The code I use would be this:
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
using std::string;
using std::fstream;
typedef struct WAV_HEADER{
char RIFF[4]; // RIFF Header Magic header
unsigned long ChunkSize; // RIFF Chunk Size
char WAVE[4]; // WAVE Header
char fmt[4]; // FMT header
unsigned long Subchunk1Size; // Size of the fmt chunk
unsigned short AudioFormat; // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
unsigned short NumOfChan; // Number of channels 1=Mono 2=Sterio
unsigned long SamplesPerSec; // Sampling Frequency in Hz
unsigned long bytesPerSec; // bytes per second
unsigned short blockAlign; // 2=16-bit mono, 4=16-bit stereo
unsigned short bitsPerSample; // Number of bits per sample
char Subchunk2ID[4]; // "data" string
unsigned long Subchunk2Size; // Sampled data length
}wav_hdr;
// Function prototypes
int getFileSize(FILE *inFile);
int main(int argc,char *argv[]){
wav_hdr wavHeader;
FILE *wavFile;
int headerSize = sizeof(wav_hdr),filelength = 0;
string answer;
do{
string input;
string answer;
const char* filePath;
cout << "Pick wav file from the Windows Media File: ";
cin >> input;
cin.get();
cout << endl;
path = "C:\\Windows\\Media\\" + input + ".wav";
filePath = path.c_str();
wavFile = fopen( filePath , "r" );
if(wavFile == NULL){
printf("Can not able to open wave file\n");
//exit(EXIT_FAILURE);
}
fread(&wavHeader,headerSize,1,wavFile);
filelength = getFileSize(wavFile);
fclose(wavFile);
cout << "File is :" << filelength << " bytes." << endl;
cout << "RIFF header :" << wavHeader.RIFF[0]
<< wavHeader.RIFF[1]
<< wavHeader.RIFF[2]
<< wavHeader.RIFF[3] << endl;
cout << "WAVE header :" << wavHeader.WAVE[0]
<< wavHeader.WAVE[1]
<< wavHeader.WAVE[2]
<< wavHeader.WAVE[3]
<< endl;
cout << "FMT :" << wavHeader.fmt[0]
<< wavHeader.fmt[1]
<< wavHeader.fmt[2]
<< wavHeader.fmt[3]
<< endl;
cout << "Data size :" << wavHeader.ChunkSize << endl;
// Display the sampling Rate form the header
cout << "Sampling Rate :" << wavHeader.SamplesPerSec << endl;
cout << "Number of bits used :" << wavHeader.bitsPerSample << endl;
cout << "Number of channels :" << wavHeader.NumOfChan << endl;
cout << "Number of bytes per second :" << wavHeader.bytesPerSec << endl;
cout << "Data length :" << wavHeader.Subchunk2Size << endl;
cout << "Audio Format :" << wavHeader.AudioFormat << endl;
// Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
cout << "Block align :" << wavHeader.blockAlign << endl;
cout << "Data string :" << wavHeader.Subchunk2ID[0]
<< wavHeader.Subchunk2ID[1]
<< wavHeader.Subchunk2ID[2]
<< wavHeader.Subchunk2ID[3]
<< endl;
cout << endl << endl << "Try something else? (y/n)";
cin >> answer;
//cin.get();
cout << endl << endl;
}while( answer == "y" );
getchar();
return 0;
}
// find the file size
int getFileSize(FILE *inFile){
int fileSize = 0;
fseek(inFile,0,SEEK_END);
fileSize=ftell(inFile);
fseek(inFile,0,SEEK_SET);
return fileSize;
}
I've tried it several times and the data it gives seems consistent through different wav files in the Media folder in the Windows folder.
The next step then would be storing the actual data of the wav file in a vector. However, I'm quite clueless on how to do this. Online solutions that I found only went as far as reading the header file.
Any ideas on how to store (and hopefully display) the actual data of the wav file? Thanks!
Upvotes: 31
Views: 88537
Reputation: 11
If you want to read a WAV file while covering cases where fmt
and data
chunks are "mixed" with other chunks:
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
struct RIFFHeader{
char chunk_id[4];
uint32_t chunk_size;
char format[4];
};
struct ChunkInfo{
char chunk_id[4];
uint32_t chunk_size;
};
struct FmtChunk{
uint16_t audio_format;
uint16_t num_channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bits_per_sample;
};
struct DataChunk
// We assume 16-bit monochannel samples
{
int16_t* data;
int nb_of_samples;
DataChunk(int s): nb_of_samples{s}, data{new int16_t[s]} {}
~DataChunk(){delete[] data;}
};
int main(){
constexpr char riff_id[4] = {'R','I','F','F'};
constexpr char format[4] = {'W','A','V','E'};
constexpr char fmt_id[4] = {'f','m','t',' '};
constexpr char data_id[4] = {'d','a','t','a'};
ifstream ifs{"../audio.wav", ios_base::binary};
if (!ifs){
cerr << "Cannot open file" << endl;
return -1;
}
// first read RIFF header
RIFFHeader h;
ifs.read((char*)(&h), sizeof(h));
if (!ifs || memcmp(h.chunk_id, riff_id, 4) || memcmp(h.format, format, 4)){
cerr << "Bad formatting" << endl;
return -1;
}
// read chunk infos iteratively
ChunkInfo ch;
bool fmt_read = false;
bool data_read = false;
while(ifs.read((char*)(&ch), sizeof(ch))){
// if fmt chunk?
if (memcmp(ch.chunk_id, fmt_id, 4) == 0){
FmtChunk fmt;
ifs.read((char*)(&fmt), ch.chunk_size);
fmt_read = true;
}
// is data chunk?
else if(memcmp(ch.chunk_id, data_id, 4) == 0){
DataChunk dat_chunk(ch.chunk_size/sizeof(int16_t));
ifs.read((char*)dat_chunk.data, ch.chunk_size);
data_read = true;
}
// otherwise skip the chunk
else{
ifs.seekg(ch.chunk_size, ios_base::cur);
}
}
if (!data_read || !fmt_read){
cout << "Problem when reading data" << endl;
return -1;
}
}
NB: I assumed mono-channel 16-bit samples here. Though it can be adapted to handle more formats...
Upvotes: 0
Reputation: 9278
This image is taken from a Stanford course
So you can see that the audio data occurs immediately after the headers you already read and there will be Subchunk2Size
bytes of audio data.
The pseudocode for this would be
ReadRIFF();
ReadFMT();
int32 chunk2Id = Read32(BigEndian);
int32 chunk2Size = Read32(LittleEndian);
for (int i = 0; i < chunk2Size; i++)
{
audioData[i] = ReadByte();
}
If the audio is stereo you'll have two audio streams in data
. If the audio is compressed (mp3, aac, etc) you'll have to decompress it first.
Upvotes: 24
Reputation: 512
I know this is an old post, but your fread parameters are switched, here is a more correct version (requires g++-4.7 or higher with -std=c++11 flag like this "g++ -std=c++11 WaveReader.cpp -o WaveReader").
#include <iostream>
#include <string>
#include <fstream>
#include <cstdint>
using std::cin;
using std::cout;
using std::endl;
using std::fstream;
using std::string;
typedef struct WAV_HEADER
{
/* RIFF Chunk Descriptor */
uint8_t RIFF[4]; // RIFF Header Magic header
uint32_t ChunkSize; // RIFF Chunk Size
uint8_t WAVE[4]; // WAVE Header
/* "fmt" sub-chunk */
uint8_t fmt[4]; // FMT header
uint32_t Subchunk1Size; // Size of the fmt chunk
uint16_t AudioFormat; // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
uint16_t NumOfChan; // Number of channels 1=Mono 2=Sterio
uint32_t SamplesPerSec; // Sampling Frequency in Hz
uint32_t bytesPerSec; // bytes per second
uint16_t blockAlign; // 2=16-bit mono, 4=16-bit stereo
uint16_t bitsPerSample; // Number of bits per sample
/* "data" sub-chunk */
uint8_t Subchunk2ID[4]; // "data" string
uint32_t Subchunk2Size; // Sampled data length
} wav_hdr;
// Function prototypes
int getFileSize(FILE* inFile);
int main(int argc, char* argv[])
{
wav_hdr wavHeader;
int headerSize = sizeof(wav_hdr), filelength = 0;
const char* filePath;
string input;
if (argc <= 1)
{
cout << "Input wave file name: ";
cin >> input;
cin.get();
filePath = input.c_str();
}
else
{
filePath = argv[1];
cout << "Input wave file name: " << filePath << endl;
}
FILE* wavFile = fopen(filePath, "r");
if (wavFile == nullptr)
{
fprintf(stderr, "Unable to open wave file: %s\n", filePath);
return 1;
}
//Read the header
size_t bytesRead = fread(&wavHeader, 1, headerSize, wavFile);
cout << "Header Read " << bytesRead << " bytes." << endl;
if (bytesRead > 0)
{
//Read the data
uint16_t bytesPerSample = wavHeader.bitsPerSample / 8; //Number of bytes per sample
uint64_t numSamples = wavHeader.ChunkSize / bytesPerSample; //How many samples are in the wav file?
static const uint16_t BUFFER_SIZE = 4096;
int8_t* buffer = new int8_t[BUFFER_SIZE];
while ((bytesRead = fread(buffer, sizeof buffer[0], BUFFER_SIZE / (sizeof buffer[0]), wavFile)) > 0)
{
/** DO SOMETHING WITH THE WAVE DATA HERE **/
cout << "Read " << bytesRead << " bytes." << endl;
}
delete [] buffer;
buffer = nullptr;
filelength = getFileSize(wavFile);
cout << "File is :" << filelength << " bytes." << endl;
cout << "RIFF header :" << wavHeader.RIFF[0] << wavHeader.RIFF[1] << wavHeader.RIFF[2] << wavHeader.RIFF[3] << endl;
cout << "WAVE header :" << wavHeader.WAVE[0] << wavHeader.WAVE[1] << wavHeader.WAVE[2] << wavHeader.WAVE[3] << endl;
cout << "FMT :" << wavHeader.fmt[0] << wavHeader.fmt[1] << wavHeader.fmt[2] << wavHeader.fmt[3] << endl;
cout << "Data size :" << wavHeader.ChunkSize << endl;
// Display the sampling Rate from the header
cout << "Sampling Rate :" << wavHeader.SamplesPerSec << endl;
cout << "Number of bits used :" << wavHeader.bitsPerSample << endl;
cout << "Number of channels :" << wavHeader.NumOfChan << endl;
cout << "Number of bytes per second :" << wavHeader.bytesPerSec << endl;
cout << "Data length :" << wavHeader.Subchunk2Size << endl;
cout << "Audio Format :" << wavHeader.AudioFormat << endl;
// Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
cout << "Block align :" << wavHeader.blockAlign << endl;
cout << "Data string :" << wavHeader.Subchunk2ID[0] << wavHeader.Subchunk2ID[1] << wavHeader.Subchunk2ID[2] << wavHeader.Subchunk2ID[3] << endl;
}
fclose(wavFile);
return 0;
}
// find the file size
int getFileSize(FILE* inFile)
{
int fileSize = 0;
fseek(inFile, 0, SEEK_END);
fileSize = ftell(inFile);
fseek(inFile, 0, SEEK_SET);
return fileSize;
}
Upvotes: 20