The WAV file format (Waveform Audio File Format) is widely used on Microsoft Windows systems for storing uncompressed audio data.
Its file extension .wav is commonly used to refer to this format.
WAV files are ideal for applications requiring high-quality audio because they store raw sound data without any compression.
What is a WAV File?
A WAV file stores audio data in a structure that mimics the attributes of sound itself. Think of it as a musical “music box” where the sound is encoded directly into the file without compression. This results in rich, high-quality audio, but it also means larger file sizes compared to formats like MP3 or FLAC.
A WAV file consists of three main components:
- RIFF Header: Describes the file format and structure.
- ‘fmt ’ Chunk: Contains metadata about the audio data (e.g., sample rate, bit depth).
- ‘data’ Chunk: Stores the actual audio samples.
WAV File Structure Breakdown
RIFF Header
The RIFF header is the first part of the WAV file. It defines the file type and overall size. Key fields include:
- Chunk ID: Always “RIFF” (4 bytes).
- Chunk Size: File size minus the first 8 bytes (4 bytes).
- Format: Always “WAVE” (4 bytes).
‘fmt ’ Chunk
This chunk specifies the format of the audio data. Key fields include:
- Audio Format: PCM = 1 (Uncompressed audio).
- Number of Channels: Mono = 1, Stereo = 2.
- Sample Rate: Number of samples per second (e.g., 44,100 Hz).
- Bits Per Sample: Common values are 16 or 24 bits.
‘data’ Chunk
This chunk contains the raw sample data, which is the actual sound information. Key fields include:
- Subchunk2 Size: Size of the audio data in bytes.
- Data: The raw audio samples.
Writing WAV Files in C++
Below is a C++ code example showing how to write a WAV file. The code focuses on creating the RIFF header, ‘fmt ’ chunk, and writing audio data. For brevity, only the core parts of the implementation are included.
Header Creation
This part writes the RIFF header and ‘fmt ’ chunk. The WAV file format uses little-endian encoding, meaning data is stored with the least significant byte first.
#include <fstream>
#include <stdexcept>
class WAV {
public:
WAV(const std::string& filename, int sampleRate, int bitsPerSample, int channels)
: file(filename, std::ios::binary), sampleRate(sampleRate), bitsPerSample(bitsPerSample), channels(channels) {
if (!file.is_open()) throw std::runtime_error("Unable to open file.");
// Write RIFF header
file.write("RIFF", 4);
file.write("\0\0\0\0", 4); // Placeholder for file size
file.write("WAVE", 4);
// Write 'fmt ' chunk
file.write("fmt ", 4);
writeLittleEndian(16, 4); // Chunk size
writeLittleEndian(1, 2); // Audio format (PCM)
writeLittleEndian(channels, 2);
writeLittleEndian(sampleRate, 4);
writeLittleEndian(sampleRate * channels * bitsPerSample / 8, 4); // Byte rate
writeLittleEndian(channels * bitsPerSample / 8, 2); // Block align
writeLittleEndian(bitsPerSample, 2);
}
private:
std::ofstream file;
int sampleRate, bitsPerSample, channels;
// Helper for writing little-endian data
template <typename T>
void writeLittleEndian(T value, unsigned size) {
while (size--) {
file.put(static_cast<char>(value & 0xFF));
value >>= 8;
}
}
};Writing Audio Data
This part stores the raw audio data in the ‘data’ chunk. The samples are normalized before being written to ensure they stay within valid amplitude bounds.
void WAV::writeData(const std::vector<std::vector<double>>& audioData) {
// Write 'data' chunk header
file.write("data", 4);
file.write("\0\0\0\0", 4); // Placeholder for data size
// Normalize and write audio samples
for (const auto& frame : audioData) {
for (double sample : frame) {
int intSample = static_cast<int>(sample * maxAmplitude());
writeLittleEndian(intSample, bitsPerSample / 8);
}
}
// Update chunk sizes
updateSizes();
}
double WAV::maxAmplitude() const {
return pow(2, bitsPerSample - 1) - 1;
}
void WAV::updateSizes() {
size_t fileSize = file.tellp();
// Update 'data' chunk size
file.seekp(40); // Position of 'data' chunk size
writeLittleEndian(fileSize - 44, 4); // 44 bytes header + 'fmt ' chunk
// Update RIFF header size
file.seekp(4); // Position of RIFF chunk size
writeLittleEndian(fileSize - 8, 4); // 8 bytes RIFF header
}Example Usage
int main() {
WAV wav("example.wav", 44100, 16, 2); // Stereo, 44.1kHz, 16-bit
std::vector<std::vector<double>> audioData(44100, std::vector<double>(2, 0.0)); // 1 second of silence
wav.writeData(audioData);
return 0;
}Why Use WAV?
- Lossless Quality: WAV files preserve the original audio data without compression, making them suitable for professional audio editing and archival purposes.
- Simple Format: The straightforward structure of WAV files makes them easy to manipulate programmatically.
Limitations
- File Size: WAV files are significantly larger than compressed formats like MP3 or AAC.
- Lack of Metadata: While metadata can be added, WAV does not natively support advanced tagging (e.g., album art, lyrics).