An RF64 file is built like a WAV file: one outer chunk that holds a list of inner
chunks, each with the same eight-byte header. What differs is the outer ID and the size
rule: any size too large for 32 bits, and the outer size always, is written as
0xFFFFFFFF, and the real value is in the
ds64 chunk. All numbers are
little-endian, least significant byte first.
The RF64 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 | RF64 (52 46 36 34) |
| 4 | 4 | u32 | ckSize | 0xFFFFFFFF; the real size, the file size minus 8, is riffSize in ds64 |
| 8 | 4 | char[4] | formType | WAVE (57 41 56 45) |
The ds64 chunk must come next, straight after WAVE at offset
12, so that a reader has the real sizes before it needs them. Readers depend on this:
FFmpeg’s probe gives an RF64 file full marks only when ds64 is at offset 12,
and libbw64, the EBU’s library, refuses a file whose first chunk is not
ds64 (“mandatory ds64 chunk for BW64 or RF64 file not found”).
The explorer reports, but still reads, a file whose outer size is not
0xFFFFFFFF (it then follows the size given), a file with no
ds64 chunk or with ds64 in another place, and a
ds64 RIFF size that does not match the size of the file.
The chunk layout
After the header 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; 0xFFFFFFFF means the real size is in ds64 |
| +8 | size | bytes | ckData | the chunk’s contents |
| +8+size | 0 or 1 | u8 | pad | a zero byte, present only when the size is odd |
The data chunk of a large file always has the size 0xFFFFFFFF and takes
its real size from dataSize in ds64. Any other chunk with that
size takes it from the ds64 table. A small RF64 file may give the data
chunk its real 32-bit size instead; the explorer uses it, and reports when
ds64 says otherwise. A chunk of 0xFFFFFFFF for which
ds64 has no size is taken to run to the end of the file, and reported.
A chunk ID that is not printable ASCII, where the next chunk should start, means the sizes before it are wrong; the explorer stops reading chunks there.
The pad byte
The pad byte keeps every chunk on an even offset. When a chunk’s size is odd, whether
the 32-bit size or the 64-bit size from ds64, one zero byte follows it and
is not counted. The same rule applies to the small chunks inside a LIST.
The example below has one after its 45 bytes of samples.
Some writers leave the pad out. 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
ds64 comes first. After it the order is as in a WAV file: the format chunk
must come before the data chunk, and other chunks can come before or after it. A reader
should look for chunks by ID and skip any it does not recognise, using the size from the
chunk header or from ds64, and the pad rule.
| Chunk | Holds | Required |
|---|---|---|
ds64 | the 64-bit sizes | yes, first |
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 |
bext | broadcast production details | no |
JUNK | reserved space, ignored | no |
A complete file
This 126-byte file is a test file from SciPy: three channels of 24-bit audio at 8,000 Hz, five frames long. These are its first 80 bytes; the samples follow.
00000000 52 46 36 34 FF FF FF FF 57 41 56 45 64 73 36 34 RF64....WAVEds64
00000010 1C 00 00 00 76 00 00 00 00 00 00 00 2D 00 00 00 ....v.......-...
00000020 00 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 66 6D 74 20 10 00 00 00 01 00 03 00 40 1F 00 00 fmt ........@...
00000040 40 19 01 00 09 00 18 00 64 61 74 61 FF FF FF FF @.......data....
| Offset | Bytes | Meaning |
|---|---|---|
| 0 | 52 46 36 34 | RF64 |
| 4 | FF FF FF FF | the outer size: look in ds64 |
| 8 | 57 41 56 45 | WAVE |
| 12 | 64 73 36 34 | ds64 chunk, first |
| 16 | 1C 00 00 00 | 28 bytes of data: no table entries |
| 20 | 76 00 00 00 00 00 00 00 | riffSize 118: the file is 126 bytes |
| 28 | 2D 00 00 00 00 00 00 00 | dataSize 45 |
| 36 | 05 00 00 00 00 00 00 00 | sampleCount 5 frames |
| 44 | 00 00 00 00 | tableLength 0 |
| 48 | 66 6D 74 20 | fmt chunk |
| 52 | 10 00 00 00 | 16 bytes of format data |
| 56 | 01 00 | format code 1, PCM |
| 58 | 03 00 | 3 channels |
| 60 | 40 1F 00 00 | 8,000 samples per second |
| 64 | 40 19 01 00 | 72,000 bytes per second |
| 68 | 09 00 | 9 bytes per frame: 3 channels × 3 bytes |
| 70 | 18 00 | 24 bits per sample |
| 72 | 64 61 74 61 | data chunk |
| 76 | FF FF FF FF | the size: look in ds64, which says 45 |
| 80 | 45 bytes | the samples: 5 frames of 9 bytes |
| 125 | 1 byte | the pad byte, since 45 is odd |
Finding the samples
- Read 12 bytes and check for
RF64andWAVE. - Read the
ds64chunk at offset 12 and keep its sizes and table. - Read an 8-byte chunk header. If its size is
0xFFFFFFFF, take the real size fromds64:dataSizefor the data chunk, the table for any other ID. - If the ID is
fmt, read the format. If it isdata, note where the samples start and how long they are. - Skip the size, plus one more byte if it is odd, and go back to step 3 until the end
of the outer chunk, whose end is
riffSize+ 8.