An Alembic archive is a tree of objects, each with a tree of properties, each property
holding samples over time. Alembic’s AbcCoreOgawa library stores this tree
in Ogawa groups and data blocks. The Alembic wiki
documents only Ogawa itself; what follows is taken from the open source of the library
that writes and reads every Ogawa Alembic file. All numbers are little-endian.
The root group
| Child | Kind | Contents |
|---|---|---|
| 0 | data | i32: the version of the AbcCoreOgawa layout, how properties are stored within Ogawa |
| 1 | data | i32: the version of the Alembic library that wrote the file, as XXYYZZ, so 10709 is 1.7.9 |
| 2 | group | the top object, the parent of every other object |
| 3 | data | the archive’s metadata, kept out of the index because it can be large |
| 4 | data | the time samplings |
| 5 | data | the indexed metadata table, or empty |
Object groups
Each object, the top object included, is a group:
| Child | Kind | Contents |
|---|---|---|
| 0 | group | the object’s properties, as a compound property group |
| 1 to n | group | its child objects, in the order they were created |
| last | data | the headers of the child objects, then 32 bytes of hashes |
The headers block holds one header per child object, in the same order:
| Size | Type | Name | Description |
|---|---|---|---|
| 4 | u32 | name length | |
| name length | chars | name | the object’s name; its full path is its parents’ names joined by / |
| 1 | u8 | metadata index | 0 for none, 1 to 254 for an entry of the indexed metadata, 255 for metadata written here |
| 4 | u32 | metadata length | only when the index is 255 |
| metadata length | chars | metadata | only when the index is 255 |
The last 32 bytes are two 16-byte hashes: one of this object’s properties and one of its children. Alembic uses them to tell whether two objects hold the same data without reading it all. The count of headers is not stored; a reader parses headers until 32 bytes remain.
Compound property groups
A compound property, including the one at child 0 of every object, is a group with one child group per property it contains, in the order they were created, and a last data child holding the property headers, one after another. A compound with no properties has no headers block.
Property headers
Each header starts with a 32-bit info word:
| Bits | Mask | Meaning |
|---|---|---|
| 0–1 | 0x00000003 | property type: 0 compound, 1 scalar, 2 array; 3 is an array whose samples each hold one element (“scalar-like”) |
| 2–3 | 0x0000000C | size hint: the fields below are 1 byte (0), 2 bytes (1) or 4 bytes (2) wide |
| 4–7 | 0x000000F0 | data type (POD), from the table below; not for compounds |
| 8 | 0x00000100 | a time sampling index follows |
| 9 | 0x00000200 | first and last changed indices follow |
| 10 | 0x00000400 | homogeneous: every array sample has the same length |
| 11 | 0x00000800 | constant: first and last changed are both 0, so only one sample is stored |
| 12–19 | 0x000FF000 | extent: numbers per element, such as 3 for a vector of three floats; not for compounds |
| 20–27 | 0x0FF00000 | metadata index: 0 none, 1 to 254 indexed, 255 inline |
| 28–31 | unused |
Then, for a scalar or array property, each field as wide as the size hint says:
| Field | Present | Description |
|---|---|---|
| number of samples | always | how many samples the property has over time |
| first changed | bit 9 | the index of the first sample that differs from sample 0 |
| last changed | bit 9 | the index of the last sample that differs from the one before; later samples repeat it |
| time sampling index | bit 8 | which time sampling the property uses; 0 when absent |
| name length | always | |
| name | always | the property’s name |
| metadata length | index 255 | |
| metadata | index 255 and length above 0 | the metadata string |
Without bit 9, first changed is 1 and last changed is one less than the number of samples: every sample is stored. A compound’s header has only the info word, the name length, the name and any inline metadata. The writer picks the size hint from the largest of the number of samples, the time sampling index, the name length and the metadata length: up to 255 gives 1 byte, up to 65,535 gives 2.
Data types
| POD | Type | Size | POD | Type | Size |
|---|---|---|---|---|---|
| 0 | boolean | 1 | 7 | unsigned 64-bit integer | 8 |
| 1 | unsigned 8-bit integer | 1 | 8 | signed 64-bit integer | 8 |
| 2 | signed 8-bit integer | 1 | 9 | 16-bit float (half) | 2 |
| 3 | unsigned 16-bit integer | 2 | 10 | 32-bit float | 4 |
| 4 | signed 16-bit integer | 2 | 11 | 64-bit float | 8 |
| 5 | unsigned 32-bit integer | 4 | 12 | string | varies |
| 6 | signed 32-bit integer | 4 | 13 | wide string | varies |
Samples
A scalar or array property is a group whose children are its stored samples. Each sample is one data block: a 16-byte key, the 128-bit MurmurHash3 (x64 variant) of the sample’s bytes, then the bytes themselves. Strings are stored one after another, each ended by a zero byte; wide strings as 32-bit characters, each ended by a zero.
A sample equal to the one before it is not written again, which is what first and last changed record: child 0 holds sample 0, children 1 onwards hold the samples from first changed to last changed, and a sample after last changed equals last changed. When a value comes back after a run of repeats, the repeated sample’s reference is written once for each skipped index, pointing at the same Ogawa data, so the run costs eight bytes per sample. A sample identical to any earlier one is referenced rather than written again.
An array property has two children per stored sample: the data block, then its dimensions. The dimensions are an empty block when the array is one-dimensional and not of strings (the length follows from the data size); otherwise they hold one u64 per dimension.
Time sampling
Child 4 of the root holds every time sampling in order; index 0 is the default, one sample per second starting at 0. Each entry:
| Size | Type | Name | Description |
|---|---|---|---|
| 4 | u32 | max sample | the largest number of samples any property using it has |
| 8 | f64 | time per cycle | seconds per cycle |
| 4 | u32 | stored times | how many times follow, at least 1 |
| 8 each | f64[] | times | the sample times in the first cycle, in seconds |
One stored time means uniform sampling, as at a frame rate: the time per cycle is the frame length. Several stored times with a real cycle length mean cyclic sampling, such as several samples within each frame for motion blur. Acyclic sampling, where every time is listed, uses a time per cycle of the largest double divided by 32.
Metadata
Metadata is a string of key=value pairs joined by semicolons, such as
schema=AbcGeom_PolyMesh_v1;schemaObjTitle=AbcGeom_PolyMesh_v1:.geom.
The archive’s own metadata, at child 3 of the root, can include:
| Key | Value |
|---|---|
_ai_AlembicVersion | the Alembic library version and build date, as text |
_ai_Application | the program that wrote the file |
_ai_DateWritten | when it was written |
_ai_Description | a description from the user |
_ai_DCC_FPS | the frame rate of the program that wrote it |
Indexed metadata
To save space, metadata strings shared by many objects and properties are stored once, at child 5 of the root, and referred to by index. The block is a list of entries, each a u8 length then that many bytes, with no terminator. Entry 1 is the first in the list; index 0 means no metadata, and 255 means the string is written in place instead. Only strings shorter than 256 bytes are indexed, and at most 254 of them; the rest are written in place.