A WAV file is a RIFF file: one outer chunk that holds a list of inner chunks. Every chunk has the same eight-byte header, so a reader can step over chunks it does not know. All numbers are little-endian, least significant byte first.
The RIFF header
The file starts with the outer chunk’s header and its form type, 12 bytes in all.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0 | 4 | char[4] | ckID | RIFF (52 49 46 46) |
| 4 | 4 | u32 | ckSize | the size of everything after this field: the file size minus 8 |
| 8 | 4 | char[4] | formType | WAVE (57 41 56 45) |
The first 44 bytes of a real 16-bit stereo recording at 44,100 Hz:
00000000 52 49 46 46 BE 47 3C 00 57 41 56 45 66 6D 74 20 RIFF.G<.WAVEfmt
00000010 10 00 00 00 01 00 02 00 44 AC 00 00 10 B1 02 00 ........D.......
00000020 04 00 10 00 64 61 74 61 74 24 3C 00 ....datat$<.
The RIFF size is 0x3C47BE, 3,950,526, so the file is 3,950,534 bytes. The
format chunk says PCM, 2 channels, 44,100 Hz, 176,400 bytes a second, 4 bytes a frame,
16 bits. The data chunk holds 0x3C2474, 3,941,492 bytes of samples; the
8,998 bytes of the outer chunk after them are further chunks.
The outer size in practice
Readers that follow ckSize stop at the end of the outer chunk, so a wrong
value hides chunks or runs off the end. The explorer reports each of these and keeps
reading:
- a size larger than the file: the file has been cut short;
- a size equal to the whole file size, 8 too many, as some writers give it (29 of the files examined for this site, from two sound-effects libraries);
- bytes, or whole chunks, after the end of the outer chunk, which readers that follow the size do not see;
- a size of 0 or
0xFFFFFFFF, as a writer leaves it when it cannot go back and fill it in; the chunks are then read to the end of the file.
The chunk layout
The rest of the file is a run of chunks, back to back. Offsets are from the start of the chunk.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| +0 | 4 | char[4] | ckID | four printable ASCII characters naming the chunk, padded with spaces, such as fmt and cue |
| +4 | 4 | u32 | ckSize | the size of the chunk’s data, not counting this header or the pad byte |
| +8 | ckSize | bytes | ckData | the chunk’s contents |
+8+ckSize | 0 or 1 | u8 | pad | a zero byte, present only when ckSize is odd |
A chunk ID that is not printable ASCII, where the next chunk should start, means the sizes before it are wrong. Among the files seen, this mostly came from files made by joining audio, where the outer size was updated but the data size was not, so more samples follow the data chunk.
The pad byte
The pad byte keeps every chunk on an even offset. It is easy to forget: an 8-bit mono
file with an odd number of samples has an odd data size, and the next
chunk starts one byte later than the size alone suggests. The same rule applies to the
small chunks inside a LIST.
Some writers leave the pad out. It was missing after an odd chunk in a few of the files seen, among them files from Awave and from old FL Studio sample packs. libsndfile always skips a pad byte. The explorer decides by looking ahead: after an odd chunk it takes the pad byte, unless a chunk header that fits starts straight away and none starts one byte later (it looks one chunk further to settle a tie), and then it reports the missing pad.
Chunk order
The 1991 specification gives the order as
RIFF('WAVE' <fmt-ck> [<fact-ck>] [<cue-ck>] [<playlist-ck>]
[<assoc-data-list>] <wave-data>)
and says that “<fmt-ck> must always occur before <wave-data>, and both of these chunks are mandatory”. In practice only that rule holds: the data cannot be read without the format. Other chunks appear before or after the data, in any order. Writers often put small metadata chunks first and the data chunk last, or append metadata after the samples once a recording has finished.
A reader should look for chunks by ID and skip any it does not recognise, using the size and the pad rule.
| Chunk | Holds | Required |
|---|---|---|
fmt | how the audio is encoded | yes |
data | the samples | yes |
fact | the number of sample frames | for every format but PCM |
LIST | text tags, or labels for cue points | no |
cue | markers at sample positions | no |
smpl | root note and loop points for samplers | no |
bext | broadcast production details | no |
JUNK | reserved space, ignored | no |
A complete file
The smallest useful WAV file is 44 bytes of headers and then the samples. This one holds four frames of 16-bit stereo audio at 44,100 Hz.
00000000 52 49 46 46 34 00 00 00 57 41 56 45 66 6D 74 20 RIFF4...WAVEfmt
00000010 10 00 00 00 01 00 02 00 44 AC 00 00 10 B1 02 00 ........D.......
00000020 04 00 10 00 64 61 74 61 10 00 00 00 00 00 00 00 ....data........
00000030 FF 7F 01 80 FF FF 01 00 00 01 00 FF ............
| Offset | Bytes | Meaning |
|---|---|---|
| 0 | 52 49 46 46 | RIFF |
| 4 | 34 00 00 00 | 52 bytes follow: the file is 60 bytes |
| 8 | 57 41 56 45 | WAVE |
| 12 | 66 6D 74 20 | fmt chunk |
| 16 | 10 00 00 00 | 16 bytes of format data |
| 20 | 01 00 | format code 1, PCM |
| 22 | 02 00 | 2 channels |
| 24 | 44 AC 00 00 | 44,100 samples per second |
| 28 | 10 B1 02 00 | 176,400 bytes per second |
| 32 | 04 00 | 4 bytes per frame |
| 34 | 10 00 | 16 bits per sample |
| 36 | 64 61 74 61 | data chunk |
| 40 | 10 00 00 00 | 16 bytes of samples: 4 frames |
| 44 | 16 bytes | the samples, decoded on Sample data |
Finding the samples
A robust reader never assumes the 44-byte layout. It walks the chunks:
- Read 12 bytes and check for
RIFFandWAVE. - Read an 8-byte chunk header.
- If the ID is
fmt, read the format. If it isdata, note where the samples start and how long they are. - Skip
ckSizebytes, plus one more ifckSizeis odd, and go back to step 2 until the end of the outer chunk or of the file.
A data size of 0 or 0xFFFFFFFF is a placeholder from a writer that
streamed the file and could not go back. FFmpeg then reads to the end of the file
(“Ignoring maximum wav data size, file may be invalid”), and libsndfile repairs a file
“which wasn’t closed properly” when the data size is 0 and the RIFF size is 8. The
explorer takes the samples to run to the end of the file and reports it.