Task 804: .WAV File Format
Task 804: .WAV File Format
WAV File Format Specifications
The .WAV (Waveform Audio File Format) is a subset of Microsoft's RIFF (Resource Interchange File Format) specification. It is primarily used for storing uncompressed audio data, though it supports compressed formats as well. The file structure consists of a RIFF header followed by chunks, with the mandatory "fmt " (format) and "data" chunks for basic PCM audio. Optional chunks like "fact" may appear for non-PCM formats or additional metadata. All multi-byte integers are stored in little-endian order unless specified otherwise (e.g., in rare big-endian "RIFX" variants). The format supports various audio encodings, but PCM (Pulse-Code Modulation) is the most common.
1. List of Properties Intrinsic to the File Format
Based on the standard specifications, the following properties represent the core fields in a .WAV file's structure. These are derived from the RIFF container and common chunks ("fmt ", "fact", and "data"). Offsets are provided for a minimal PCM file; actual offsets may vary if additional chunks are present. Properties are listed with their byte sizes, typical offsets (for reference in a basic file), and descriptions. Only header and metadata properties are included, excluding the raw audio data itself.
- ChunkID: 4 bytes, offset 0, description: Identifier for the RIFF container, must be "RIFF" (ASCII: 0x52494646) for little-endian files.
- ChunkSize: 4 bytes, offset 4, description: Size of the entire file minus 8 bytes (little-endian uint32_t), calculated as 4 + (8 + fmt chunk size) + (8 + data chunk size) + sizes of any other chunks.
- Format: 4 bytes, offset 8, description: WAVE identifier, must be "WAVE" (ASCII: 0x57415645).
- Subchunk1ID (fmt chunk ID): 4 bytes, offset 12, description: Identifier for the format chunk, must be "fmt " (ASCII: 0x666d7420).
- Subchunk1Size (fmt chunk size): 4 bytes, offset 16, description: Size of the fmt chunk data (little-endian uint32_t); typically 16 for PCM, 18 for non-PCM without extension, or 40 for extensible formats.
- AudioFormat: 2 bytes, offset 20, description: Audio format code (little-endian uint16_t); 1 for PCM, 3 for IEEE float, 0xFFFE for extensible, etc.
- NumChannels: 2 bytes, offset 22, description: Number of audio channels (little-endian uint16_t); e.g., 1 for mono, 2 for stereo.
- SampleRate: 4 bytes, offset 24, description: Sampling rate in Hz (little-endian uint32_t); e.g., 44100 for CD quality.
- ByteRate: 4 bytes, offset 28, description: Average bytes per second (little-endian uint32_t); calculated as SampleRate × NumChannels × (BitsPerSample / 8).
- BlockAlign: 2 bytes, offset 32, description: Bytes per sample block across all channels (little-endian uint16_t); calculated as NumChannels × (BitsPerSample / 8).
- BitsPerSample: 2 bytes, offset 34, description: Bits per sample (little-endian uint16_t); e.g., 8, 16, 24, or 32; defines the container size.
- cbSize (extension size, optional): 2 bytes, offset 36 (if Subchunk1Size > 16), description: Size of the format extension (little-endian uint16_t); 0 or absent for basic PCM, 22 for extensible formats.
- wValidBitsPerSample (optional): 2 bytes, offset 38 (if cbSize > 0), description: Number of valid bits per sample (little-endian uint16_t); <= BitsPerSample, used in extensible formats.
- dwChannelMask (optional): 4 bytes, offset 40 (if cbSize > 0), description: Bitmask for channel-to-speaker mapping (little-endian uint32_t); e.g., 0x03 for front left/right.
- SubFormat (optional): 16 bytes, offset 44 (if cbSize > 0), description: GUID specifying the subformat (e.g., for PCM: first 2 bytes = 0x0001, followed by fixed bytes).
- factChunkID (optional): 4 bytes, variable offset (after fmt if present), description: Identifier for the fact chunk, "fact" (ASCII: 0x66616374); required for non-PCM.
- factChunkSize (optional): 4 bytes, following factChunkID, description: Size of fact chunk data (little-endian uint32_t); minimum 4.
- dwSampleLength (optional): 4 bytes, following factChunkSize, description: Number of samples per channel (little-endian uint32_t).
- Subchunk2ID (data chunk ID): 4 bytes, variable offset (typically 36 or later), description: Identifier for the data chunk, "data" (ASCII: 0x64617461).
- Subchunk2Size (data chunk size): 4 bytes, following Subchunk2ID, description: Size of the audio data in bytes (little-endian uint32_t).
Note: Additional chunks (e.g., "LIST", "INFO") may exist but are not intrinsic to the core audio format. The above focuses on essential properties for audio playback and storage.
2. Two Direct Download Links for .WAV Files
- https://file-examples.com/wp-content/storage/2017/11/file_example_WAV_1MG.wav (approximately 1 MB sample file)
- https://file-examples.com/wp-content/storage/2017/11/file_example_WAV_2MG.wav (approximately 2 MB sample file)
3. HTML/JavaScript for Drag-and-Drop .WAV File Property Dump
The following is a self-contained HTML snippet with embedded JavaScript that can be embedded in a blog post (e.g., on Ghost or similar platforms). It creates a drop zone where users can drag and drop a .WAV file. The script reads the file as an ArrayBuffer, parses the properties listed above (assuming a basic PCM structure; handles optional fields if present), and displays them on the screen. It skips the raw audio data.
4. Python Class for .WAV File Handling
The following Python class uses the struct module to read and parse a .WAV file, decode the properties, print them to the console, and write a new .WAV file (copying the original data for simplicity). It handles basic PCM and optional fields.
import struct
import os
class WavHandler:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
self.audio_data = b''
self.read_and_decode()
def read_and_decode(self):
with open(self.filepath, 'rb') as f:
data = f.read()
offset = 0
def unpack(fmt, size):
nonlocal offset
val = struct.unpack_from(fmt, data, offset)[0]
offset += size
return val
def unpack_str(size):
return unpack('<%ds' % size, size).decode('ascii').rstrip('\x00')
self.properties['ChunkID'] = unpack_str(4)
self.properties['ChunkSize'] = unpack('<I', 4)
self.properties['Format'] = unpack_str(4)
self.properties['Subchunk1ID'] = unpack_str(4)
fmt_size = unpack('<I', 4)
self.properties['Subchunk1Size'] = fmt_size
self.properties['AudioFormat'] = unpack('<H', 2)
self.properties['NumChannels'] = unpack('<H', 2)
self.properties['SampleRate'] = unpack('<I', 4)
self.properties['ByteRate'] = unpack('<I', 4)
self.properties['BlockAlign'] = unpack('<H', 2)
self.properties['BitsPerSample'] = unpack('<H', 2)
if fmt_size > 16:
cb_size = unpack('<H', 2)
self.properties['cbSize'] = cb_size
if fmt_size >= 40:
self.properties['wValidBitsPerSample'] = unpack('<H', 2)
self.properties['dwChannelMask'] = unpack('<I', 4)
sub_format = ' '.join(f'{b:02x}' for b in data[offset:offset+16])
self.properties['SubFormat'] = sub_format
offset += 16
while offset < len(data):
chunk_id = unpack_str(4)
chunk_size = unpack('<I', 4)
if chunk_id == 'fact':
self.properties['factChunkID'] = chunk_id
self.properties['factChunkSize'] = chunk_size
self.properties['dwSampleLength'] = unpack('<I', 4)
elif chunk_id == 'data':
self.properties['Subchunk2ID'] = chunk_id
self.properties['Subchunk2Size'] = chunk_size
self.audio_data = data[offset:offset + chunk_size]
offset += chunk_size
if chunk_size % 2 != 0:
offset += 1 # Pad byte
break
else:
offset += chunk_size # Skip unknown
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, output_path):
with open(output_path, 'wb') as f:
def pack(fmt, val):
return struct.pack(fmt, val)
f.write(pack('<4s', self.properties['ChunkID'].encode('ascii')))
f.write(pack('<I', self.properties['ChunkSize']))
f.write(pack('<4s', self.properties['Format'].encode('ascii')))
f.write(pack('<4s', self.properties['Subchunk1ID'].encode('ascii')))
f.write(pack('<I', self.properties['Subchunk1Size']))
f.write(pack('<H', self.properties['AudioFormat']))
f.write(pack('<H', self.properties['NumChannels']))
f.write(pack('<I', self.properties['SampleRate']))
f.write(pack('<I', self.properties['ByteRate']))
f.write(pack('<H', self.properties['BlockAlign']))
f.write(pack('<H', self.properties['BitsPerSample']))
if 'cbSize' in self.properties:
f.write(pack('<H', self.properties['cbSize']))
if 'wValidBitsPerSample' in self.properties:
f.write(pack('<H', self.properties['wValidBitsPerSample']))
f.write(pack('<I', self.properties['dwChannelMask']))
sub_format_bytes = bytes(int(b, 16) for b in self.properties['SubFormat'].split())
f.write(sub_format_bytes)
if 'factChunkID' in self.properties:
f.write(pack('<4s', self.properties['factChunkID'].encode('ascii')))
f.write(pack('<I', self.properties['factChunkSize']))
f.write(pack('<I', self.properties['dwSampleLength']))
f.write(pack('<4s', self.properties['Subchunk2ID'].encode('ascii')))
f.write(pack('<I', self.properties['Subchunk2Size']))
f.write(self.audio_data)
if self.properties['Subchunk2Size'] % 2 != 0:
f.write(b'\x00') # Pad byte
# Example usage:
# handler = WavHandler('input.wav')
# handler.print_properties()
# handler.write('output.wav')
5. Java Class for .WAV File Handling
The following Java class uses DataInputStream to read and parse a .WAV file, decode the properties, print them to the console, and write a new .WAV file using DataOutputStream. It assumes a basic structure and handles optional fields.
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class WavHandler {
private String filepath;
private Map<String, Object> properties = new HashMap<>();
private byte[] audioData;
public WavHandler(String filepath) {
this.filepath = filepath;
readAndDecode();
}
private void readAndDecode() {
try (FileInputStream fis = new FileInputStream(filepath);
DataInputStream dis = new DataInputStream(fis)) {
properties.put("ChunkID", readString(dis, 4));
properties.put("ChunkSize", dis.readInt());
properties.put("Format", readString(dis, 4));
properties.put("Subchunk1ID", readString(dis, 4));
int fmtSize = dis.readInt();
properties.put("Subchunk1Size", fmtSize);
properties.put("AudioFormat", dis.readShort());
properties.put("NumChannels", dis.readShort());
properties.put("SampleRate", dis.readInt());
properties.put("ByteRate", dis.readInt());
properties.put("BlockAlign", dis.readShort());
properties.put("BitsPerSample", dis.readShort());
if (fmtSize > 16) {
short cbSize = dis.readShort();
properties.put("cbSize", cbSize);
if (fmtSize >= 40) {
properties.put("wValidBitsPerSample", dis.readShort());
properties.put("dwChannelMask", dis.readInt());
byte[] subFormat = new byte[16];
dis.readFully(subFormat);
StringBuilder sb = new StringBuilder();
for (byte b : subFormat) sb.append(String.format("%02x ", b));
properties.put("SubFormat", sb.toString().trim());
}
}
// Read remaining chunks
while (dis.available() > 0) {
String chunkId = readString(dis, 4);
int chunkSize = dis.readInt();
if (chunkId.equals("fact")) {
properties.put("factChunkID", chunkId);
properties.put("factChunkSize", chunkSize);
properties.put("dwSampleLength", dis.readInt());
} else if (chunkId.equals("data")) {
properties.put("Subchunk2ID", chunkId);
properties.put("Subchunk2Size", chunkSize);
audioData = new byte[chunkSize];
dis.readFully(audioData);
if (chunkSize % 2 != 0) dis.skip(1); // Pad byte
break;
} else {
dis.skip(chunkSize); // Skip unknown
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private String readString(DataInputStream dis, int length) throws IOException {
byte[] bytes = new byte[length];
dis.readFully(bytes);
return new String(bytes, "ASCII").trim();
}
public void printProperties() {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(String outputPath) {
try (FileOutputStream fos = new FileOutputStream(outputPath);
DataOutputStream dos = new DataOutputStream(fos)) {
dos.writeBytes((String) properties.get("ChunkID"));
dos.writeInt((Integer) properties.get("ChunkSize"));
dos.writeBytes((String) properties.get("Format"));
dos.writeBytes((String) properties.get("Subchunk1ID"));
dos.writeInt((Integer) properties.get("Subchunk1Size"));
dos.writeShort((Short) properties.get("AudioFormat"));
dos.writeShort((Short) properties.get("NumChannels"));
dos.writeInt((Integer) properties.get("SampleRate"));
dos.writeInt((Integer) properties.get("ByteRate"));
dos.writeShort((Short) properties.get("BlockAlign"));
dos.writeShort((Short) properties.get("BitsPerSample"));
if (properties.containsKey("cbSize")) {
dos.writeShort((Short) properties.get("cbSize"));
if (properties.containsKey("wValidBitsPerSample")) {
dos.writeShort((Short) properties.get("wValidBitsPerSample"));
dos.writeInt((Integer) properties.get("dwChannelMask"));
String[] hex = ((String) properties.get("SubFormat")).split(" ");
for (String h : hex) dos.writeByte((byte) Integer.parseInt(h, 16));
}
}
if (properties.containsKey("factChunkID")) {
dos.writeBytes((String) properties.get("factChunkID"));
dos.writeInt((Integer) properties.get("factChunkSize"));
dos.writeInt((Integer) properties.get("dwSampleLength"));
}
dos.writeBytes((String) properties.get("Subchunk2ID"));
dos.writeInt((Integer) properties.get("Subchunk2Size"));
dos.write(audioData);
if ((Integer) properties.get("Subchunk2Size") % 2 != 0) {
dos.writeByte(0); // Pad byte
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Example usage:
// public static void main(String[] args) {
// WavHandler handler = new WavHandler("input.wav");
// handler.printProperties();
// handler.write("output.wav");
// }
}
6. JavaScript Class for .WAV File Handling
The following JavaScript class (for Node.js) reads and parses a .WAV file using fs, decodes the properties, prints them to the console, and writes a new .WAV file. It handles basic structures.
const fs = require('fs');
class WavHandler {
constructor(filepath) {
this.filepath = filepath;
this.properties = {};
this.audioData = Buffer.alloc(0);
this.readAndDecode();
}
readAndDecode() {
const data = fs.readFileSync(this.filepath);
let offset = 0;
const readString = (len) => {
const str = data.toString('ascii', offset, offset + len).trim();
offset += len;
return str;
};
const readUint16 = () => {
const val = data.readUInt16LE(offset);
offset += 2;
return val;
};
const readUint32 = () => {
const val = data.readUInt32LE(offset);
offset += 4;
return val;
};
this.properties.ChunkID = readString(4);
this.properties.ChunkSize = readUint32();
this.properties.Format = readString(4);
this.properties.Subchunk1ID = readString(4);
const fmtSize = readUint32();
this.properties.Subchunk1Size = fmtSize;
this.properties.AudioFormat = readUint16();
this.properties.NumChannels = readUint16();
this.properties.SampleRate = readUint32();
this.properties.ByteRate = readUint32();
this.properties.BlockAlign = readUint16();
this.properties.BitsPerSample = readUint16();
if (fmtSize > 16) {
this.properties.cbSize = readUint16();
if (fmtSize >= 40) {
this.properties.wValidBitsPerSample = readUint16();
this.properties.dwChannelMask = readUint32();
let subFormat = '';
for (let i = 0; i < 16; i++) {
subFormat += data.readUInt8(offset + i).toString(16).padStart(2, '0') + ' ';
}
this.properties.SubFormat = subFormat.trim();
offset += 16;
}
}
while (offset < data.length) {
const chunkId = readString(4);
const chunkSize = readUint32();
if (chunkId === 'fact') {
this.properties.factChunkID = chunkId;
this.properties.factChunkSize = chunkSize;
this.properties.dwSampleLength = readUint32();
} else if (chunkId === 'data') {
this.properties.Subchunk2ID = chunkId;
this.properties.Subchunk2Size = chunkSize;
this.audioData = data.slice(offset, offset + chunkSize);
offset += chunkSize;
if (chunkSize % 2 !== 0) offset += 1;
break;
} else {
offset += chunkSize;
}
}
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key}: ${value}`);
}
}
write(outputPath) {
const buffer = Buffer.alloc(this.properties.ChunkSize + 8);
let offset = 0;
const writeString = (str, len) => {
buffer.write(str, offset, len, 'ascii');
offset += len;
};
const writeUint16 = (val) => {
buffer.writeUInt16LE(val, offset);
offset += 2;
};
const writeUint32 = (val) => {
buffer.writeUInt32LE(val, offset);
offset += 4;
};
writeString(this.properties.ChunkID, 4);
writeUint32(this.properties.ChunkSize);
writeString(this.properties.Format, 4);
writeString(this.properties.Subchunk1ID, 4);
writeUint32(this.properties.Subchunk1Size);
writeUint16(this.properties.AudioFormat);
writeUint16(this.properties.NumChannels);
writeUint32(this.properties.SampleRate);
writeUint32(this.properties.ByteRate);
writeUint16(this.properties.BlockAlign);
writeUint16(this.properties.BitsPerSample);
if (this.properties.cbSize !== undefined) {
writeUint16(this.properties.cbSize);
if (this.properties.wValidBitsPerSample !== undefined) {
writeUint16(this.properties.wValidBitsPerSample);
writeUint32(this.properties.dwChannelMask);
const hex = this.properties.SubFormat.split(' ');
for (const h of hex) buffer.writeUInt8(parseInt(h, 16), offset++);
}
}
if (this.properties.factChunkID !== undefined) {
writeString(this.properties.factChunkID, 4);
writeUint32(this.properties.factChunkSize);
writeUint32(this.properties.dwSampleLength);
}
writeString(this.properties.Subchunk2ID, 4);
writeUint32(this.properties.Subchunk2Size);
this.audioData.copy(buffer, offset);
offset += this.audioData.length;
if (this.properties.Subchunk2Size % 2 !== 0) {
buffer.writeUInt8(0, offset);
}
fs.writeFileSync(outputPath, buffer);
}
}
// Example usage:
// const handler = new WavHandler('input.wav');
// handler.printProperties();
// handler.write('output.wav');
7. C++ Class for .WAV File Handling
The following C++ class uses std::ifstream and std::ofstream to read and parse a .WAV file, decode the properties, print them to the console, and write a new .WAV file. It handles basic structures using a std::map for properties.
#include <iostream>
#include <fstream>
#include <map>
#include <vector>
#include <iomanip>
#include <string>
class WavHandler {
private:
std::string filepath;
std::map<std::string, std::string> properties;
std::vector<char> audioData;
public:
WavHandler(const std::string& filepath) : filepath(filepath) {
readAndDecode();
}
void readAndDecode() {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) return;
size_t size = file.tellg();
file.seekg(0);
std::vector<char> data(size);
file.read(data.data(), size);
size_t offset = 0;
auto readString = [&](size_t len) -> std::string {
std::string str(data.begin() + offset, data.begin() + offset + len);
offset += len;
// Trim nulls
str.erase(std::find(str.begin(), str.end(), '\0'), str.end());
return str;
};
auto readUint16 = [&]() -> uint16_t {
uint16_t val = *reinterpret_cast<uint16_t*>(data.data() + offset);
offset += 2;
return val;
};
auto readUint32 = [&]() -> uint32_t {
uint32_t val = *reinterpret_cast<uint32_t*>(data.data() + offset);
offset += 4;
return val;
};
properties["ChunkID"] = readString(4);
properties["ChunkSize"] = std::to_string(readUint32());
properties["Format"] = readString(4);
properties["Subchunk1ID"] = readString(4);
uint32_t fmtSize = readUint32();
properties["Subchunk1Size"] = std::to_string(fmtSize);
properties["AudioFormat"] = std::to_string(readUint16());
properties["NumChannels"] = std::to_string(readUint16());
properties["SampleRate"] = std::to_string(readUint32());
properties["ByteRate"] = std::to_string(readUint32());
properties["BlockAlign"] = std::to_string(readUint16());
properties["BitsPerSample"] = std::to_string(readUint16());
if (fmtSize > 16) {
uint16_t cbSize = readUint16();
properties["cbSize"] = std::to_string(cbSize);
if (fmtSize >= 40) {
properties["wValidBitsPerSample"] = std::to_string(readUint16());
properties["dwChannelMask"] = std::to_string(readUint32());
std::stringstream ss;
for (size_t i = 0; i < 16; ++i) {
ss << std::hex << std::setw(2) << std::setfill('0') << (static_cast<unsigned char>(data[offset + i]) & 0xff) << " ";
}
properties["SubFormat"] = ss.str();
offset += 16;
}
}
while (offset < size) {
std::string chunkId = readString(4);
uint32_t chunkSize = readUint32();
if (chunkId == "fact") {
properties["factChunkID"] = chunkId;
properties["factChunkSize"] = std::to_string(chunkSize);
properties["dwSampleLength"] = std::to_string(readUint32());
} else if (chunkId == "data") {
properties["Subchunk2ID"] = chunkId;
properties["Subchunk2Size"] = std::to_string(chunkSize);
audioData.assign(data.begin() + offset, data.begin() + offset + chunkSize);
offset += chunkSize;
if (chunkSize % 2 != 0) ++offset;
break;
} else {
offset += chunkSize;
}
}
}
void printProperties() const {
for (const auto& pair : properties) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
}
void write(const std::string& outputPath) const {
std::ofstream file(outputPath, std::ios::binary);
if (!file) return;
auto writeString = [&](const std::string& str, size_t len) {
file.write(str.c_str(), len);
};
auto writeUint16 = [&](uint16_t val) {
file.write(reinterpret_cast<const char*>(&val), 2);
};
auto writeUint32 = [&](uint32_t val) {
file.write(reinterpret_cast<const char*>(&val), 4);
};
writeString(properties.at("ChunkID"), 4);
writeUint32(std::stoul(properties.at("ChunkSize")));
writeString(properties.at("Format"), 4);
writeString(properties.at("Subchunk1ID"), 4);
writeUint32(std::stoul(properties.at("Subchunk1Size")));
writeUint16(std::stoi(properties.at("AudioFormat")));
writeUint16(std::stoi(properties.at("NumChannels")));
writeUint32(std::stoul(properties.at("SampleRate")));
writeUint32(std::stoul(properties.at("ByteRate")));
writeUint16(std::stoi(properties.at("BlockAlign")));
writeUint16(std::stoi(properties.at("BitsPerSample")));
if (properties.count("cbSize")) {
writeUint16(std::stoi(properties.at("cbSize")));
if (properties.count("wValidBitsPerSample")) {
writeUint16(std::stoi(properties.at("wValidBitsPerSample")));
writeUint32(std::stoul(properties.at("dwChannelMask")));
std::istringstream iss(properties.at("SubFormat"));
std::string hex;
while (iss >> hex) {
unsigned char byte = static_cast<unsigned char>(std::stoi(hex, nullptr, 16));
file.write(reinterpret_cast<const char*>(&byte), 1);
}
}
}
if (properties.count("factChunkID")) {
writeString(properties.at("factChunkID"), 4);
writeUint32(std::stoul(properties.at("factChunkSize")));
writeUint32(std::stoul(properties.at("dwSampleLength")));
}
writeString(properties.at("Subchunk2ID"), 4);
uint32_t dataSize = std::stoul(properties.at("Subchunk2Size"));
writeUint32(dataSize);
file.write(audioData.data(), dataSize);
if (dataSize % 2 != 0) {
char pad = 0;
file.write(&pad, 1);
}
}
};
// Example usage:
// int main() {
// WavHandler handler("input.wav");
// handler.printProperties();
// handler.write("output.wav");
// return 0;
// }