IAMF Explained: A Technical Deep Dive Into Eclipsa Audio, With Code
Spatial9 Team · · Updated · 20 min read
Key takeaways
- An IAMF stream is a sequence of OBUs: four descriptor types describe the content once, then Parameter Blocks and Audio Frames carry it over time.
- IAMF wraps existing codecs (Opus, AAC-LC, FLAC and LPCM) instead of defining a new one, and scalable channel layers let one stream serve stereo up to 7.1.4 and beyond.
- Version 2.0.0, approved in August 2026, adds object-based audio, animated positions, two codecs per stream and three new profiles.
- libiamf is the reference C decoder with head tracking, and iamf-tools encodes from WAV or ADM BWF using readable textproto configuration.
Most writing about Eclipsa Audio stays at the level of "it is open and it plays on YouTube". That is true, and we cover the artist side in Eclipsa Audio vs Dolby Atmos and the licensing side in why open source matters for immersive audio. This article goes one level down. It walks through every major structure in the IAMF bitstream, with the syntax and source code quoted directly from the official AOMediaCodec repositories on GitHub.
Every snippet below is copied from a pinned commit, so the code will still match the link next year. The spec syntax comes from the iamf repository, the encoder code from iamf-tools and the decoder API from libiamf. All three are published by the Alliance for Open Media under the BSD 3-Clause Clear License and the AOM Patent License 1.0. If you only need to publish a mix, our practical guide to Eclipsa Audio on YouTube covers the free conversion routes and how to listen on a computer.
IAMF at a glance
| Feature | What IAMF defines |
|---|---|
| Full name | Immersive Audio Model and Formats |
| Publisher | Alliance for Open Media (AOM) |
| Consumer name | Eclipsa Audio |
| Specification versions | v1.0.0 (2023), v1.1.0 (2024), v2.0.0 (approved August 2026) |
| Packet unit | OBU, Open Bitstream Unit, with a 5-bit type |
| Codecs | Opus, AAC-LC, FLAC, LPCM |
| Audio element types | Channel-based, scene-based (Ambisonics), object-based from v2.0.0 |
| Largest channel layouts | 9.1.6, 7.1.5.4 and 22.2 (10.2.9.3 in v2.0.0) |
| Ambisonics | Up to 14th order, 225 channels |
| Profiles | Simple, Base, Base-Enhanced, Base-Advanced, Advanced-1, Advanced-2 |
| Rendering | Open Audio Renderer (OAR) specification, speakers or binaural |
| Container | Standalone .iamf, or MP4 with an 'iamf' sample entry |
| Reference software | iamf-tools (encoder, decoder, web demo) and libiamf (decoder) |
| Licence | BSD 3-Clause Clear plus the AOM Patent License 1.0, royalty-free |
The architecture: descriptors, then temporal units
An IAMF file is called an IA Sequence. It opens with descriptor OBUs that tell a decoder everything it needs before the first sample: which profile the stream follows, which codec each substream uses, what the audio elements are and how they should be mixed. After that, the stream is a run of temporal units. Each unit holds the Parameter Blocks and Audio Frames for one slice of time.
The split matters for streaming. A player can read the descriptors once, set up its decoders and renderer, and then treat everything else as a steady flow of frames. Seeking means jumping to a new temporal unit, not re-reading the whole file.
The OBU header
Every OBU starts with the same header. The syntax below is quoted from the IAMF specification source:
class OBUHeader() {
unsigned int (5) obu_type;
unsigned int (1) obu_redundant_copy;
if (obu_type == 2) {
unsigned int (1) optional_fields_flag;
}
else if (obu_type == 4) {
unsigned int (1) is_not_key_frame;
}
else if (obu_type > 4 && obu_type < 24) {
unsigned int (1) obu_trimming_status_flag;
}
else {
unsigned int (1) reserved;
}
unsigned int (1) obu_extension_flag;
leb128() obu_size;
if ((obu_type > 4 && obu_type < 24) && obu_trimming_status_flag) {
leb128() num_samples_to_trim_at_end;
leb128() num_samples_to_trim_at_start;
}
if (obu_extension_flag) {
leb128() extension_header_size;
unsigned int (8 x extension_header_size) extension_header_bytes;
}
}
Source: AOMediaCodec/iamf, index.bs, OBU Header syntax.
Three details are worth knowing. First, obu_size is a leb128 value, the same variable-length integer used in AV1, so small OBUs cost one byte of size field. Second, the trimming fields let an encoder mark priming samples at the start and padding at the end of a track, which is how IAMF reaches sample-accurate length with codecs that work in fixed frames. Third, obu_redundant_copy allows descriptors to be repeated in a live stream so a player that joins late can still start.
In iamf-tools, the OBU types are a plain C++ enum:
enum ObuType : uint8_t {
kObuIaCodecConfig = 0,
kObuIaAudioElement = 1,
kObuIaMixPresentation = 2,
kObuIaParameterBlock = 3,
kObuIaTemporalDelimiter = 4,
kObuIaAudioFrame = 5,
kObuIaAudioFrameId0 = 6,
kObuIaAudioFrameId1 = 7,
// ... kObuIaAudioFrameId2 to kObuIaAudioFrameId16 ...
kObuIaAudioFrameId17 = 23,
kObuIaMetadata = 24,
kObuIaReserved25 = 25,
// ... kObuIaReserved26 to kObuIaReserved30 ...
kObuIaSequenceHeader = 31,
};
Source: AOMediaCodec/iamf-tools, iamf/obu/obu_header.h. Repeated lines are shortened with comments.
Types 6 to 23 are a compact trick. Instead of writing a substream ID inside every audio frame, the first 18 substreams can be identified by the OBU type itself, which saves a byte or two per frame across thousands of frames. Type 24, the Metadata OBU, is new in v2.0.0 and carries ITU-T T.35 data and IAMF tags.
The IA Sequence Header and profiles
The first OBU in every sequence is the IA Sequence Header. It is tiny:
class IASequenceHeaderOBU() {
unsigned int (32) ia_code;
unsigned int (8) primary_profile;
unsigned int (8) additional_profile;
}
Source: AOMediaCodec/iamf, index.bs, IA Sequence Header OBU syntax.
ia_code is always the four characters "iamf". The specification points out a useful side effect: with no extension and no trimming, the header bytes before that code are always 0xF806 or 0xFC06, so a parser can find the start of a sequence inside a raw byte stream by searching for that pattern.
primary_profile is the profile a decoder must support to play the stream. additional_profile names a richer profile that a more capable decoder can use, so one stream can serve a basic phone decoder and a full home theatre decoder at once. iamf-tools lists the values like this:
enum class ProfileVersion : uint8_t {
kIamfSimpleProfile = 0,
kIamfBaseProfile = 1,
kIamfBaseEnhancedProfile = 2,
kIamfBaseAdvancedProfile = 3,
kIamfAdvanced1Profile = 4,
kIamfAdvanced2Profile = 5,
kIamfReserved255Profile = 255
};
Source: AOMediaCodec/iamf-tools, iamf/obu/ia_sequence_header.h.
| Profile | Value | Introduced | Max channels | Max audio elements | Typical use |
|---|---|---|---|---|---|
| Simple | 0 | v1.0.0 | 16 | 1 | One 3rd-order Ambisonics scene, or one channel layout |
| Base | 1 | v1.0.0 | 18 | 2 | A scene plus a stereo element, such as commentary |
| Base-Enhanced | 2 | v1.1.0 | 28 | 28 | 3rd-order Ambisonics plus a 7.1.4 bed |
| Base-Advanced | 3 | v2.0.0 | 18 | 18 | Object-only first mix, two codecs with one LPCM |
| Advanced-1 | 4 | v2.0.0 | 18 | 18 | Any mix of channel, scene and object elements |
| Advanced-2 | 5 | v2.0.0 | 28 | 28 | The same, at the larger channel budget |
Codec Config: one stream, four codecs
IAMF does not invent a new audio codec. It wraps existing ones, which is a large part of why it was cheap to deploy on YouTube and on TVs that already decode Opus and AAC. The Codec Config OBU sets one up:
class CodecConfigOBU() {
leb128() codec_config_id;
CodecConfig codec_config;
}
class CodecConfig() {
unsigned int (32) codec_id;
leb128() num_samples_per_frame;
signed int (16) audio_roll_distance;
DecoderConfig decoder_config(codec_id);
}
Source: AOMediaCodec/iamf, index.bs, Codec Config OBU syntax.
codec_id is a four-character code. iamf-tools spells them out as hex:
enum CodecId : uint32_t {
kCodecIdOpus = 0x4f707573, // "Opus"
kCodecIdFlac = 0x664c6143, // "fLaC"
kCodecIdLpcm = 0x6970636d, // "ipcm"
kCodecIdAacLc = 0x6d703461, // "mp4a"
};
Source: AOMediaCodec/iamf-tools, iamf/obu/codec_config.h.
audio_roll_distance tells a decoder how many frames before a seek point it must decode to get clean output, which matters for Opus and AAC because their frames overlap. Lossless FLAC and LPCM need no roll and no reconstruction gain. In v1.x a sequence uses one codec. Version 2.0.0 allows two Codec Config OBUs, provided one of them is LPCM, so a lossy music bed can sit next to uncompressed system sounds.
Audio elements: channels, scenes and objects
An audio element is one piece of content that is rendered as a unit: a 7.1.4 music bed, an Ambisonics scene, a commentary track or a single moving object. Here is the start of its OBU:
class AudioElementOBU() {
leb128() audio_element_id;
unsigned int (3) audio_element_type;
unsigned int (5) reserved_for_future_use;
leb128() codec_config_id;
leb128() num_substreams;
for (i = 0; i < num_substreams; i++) {
leb128() audio_substream_id;
}
leb128() num_parameters;
for (i = 0; i < num_parameters; i++) {
leb128() param_definition_type;
if (param_definition_type == PARAMETER_DEFINITION_DEMIXING) {
DemixingParamDefinition demixing_info;
}
else if (param_definition_type == PARAMETER_DEFINITION_RECON_GAIN) {
ReconGainParamDefinition recon_gain_info;
}
else if (param_definition_type > 9) {
leb128() param_definition_size;
unsigned int (8 x param_definition_size) param_definition_bytes;
}
}
if (audio_element_type == CHANNEL_BASED) {
ScalableChannelLayoutConfig scalable_channel_layout_config;
}
else if (audio_element_type == SCENE_BASED) {
AmbisonicsConfig ambisonics_config;
// ... object-based and extension branches follow in the spec ...
Source: AOMediaCodec/iamf, index.bs, Audio Element OBU syntax (truncated).
Notice the param_definition_type > 9 branch. Any parameter type a decoder does not understand comes with its own size, so an older decoder can skip it cleanly. That rule is how v2.0.0 added object positions without breaking v1 decoders. The element types in iamf-tools:
enum AudioElementType : uint8_t {
kAudioElementChannelBased = 0,
kAudioElementSceneBased = 1,
kAudioElementObjectBased = 2,
// Values in the range of [3 - 7] are reserved.
kAudioElementBeginReserved = 3,
kAudioElementEndReserved = 7,
};
Source: AOMediaCodec/iamf-tools, iamf/obu/audio_element.h.
Channel-based elements and scalable layouts
A channel-based element is not one fixed layout. It is a stack of up to six channel groups, each adding speakers to the one below:
class ScalableChannelLayoutConfig() {
unsigned int (3) num_layers;
unsigned int (5) reserved_for_future_use;
for (i = 1; i <= num_layers; i++) {
ChannelAudioLayerConfig channel_audio_layer_config(i);
}
}
class ChannelAudioLayerConfig(i) {
unsigned int (4) loudspeaker_layout(i);
unsigned int (1) output_gain_is_present_flag(i);
unsigned int (1) recon_gain_is_present_flag(i);
unsigned int (2) reserved_for_future_use;
unsigned int (8) substream_count(i);
unsigned int (8) coupled_substream_count(i);
if (output_gain_is_present_flag(i) == 1) {
unsigned int (6) output_gain_flags(i);
unsigned int (2) reserved_for_future_use;
signed int (16) output_gain(i);
}
if (i == 1 && loudspeaker_layout == 15)
unsigned int (8) expanded_loudspeaker_layout;
}
Source: AOMediaCodec/iamf, index.bs, Scalable Channel Layout Config syntax.
The 4-bit loudspeaker_layout maps to these layouts in iamf-tools, with the channel order in the comments:
enum LoudspeakerLayout : uint8_t {
kLayoutMono = 0, // C.
kLayoutStereo = 1, // L/R
kLayout5_1_ch = 2, // L/C/R/Ls/Rs/LFE.
kLayout5_1_2_ch = 3, // L/C/R/Ls/Rs/Ltf/Rtf/LFE.
kLayout5_1_4_ch = 4, // L/C/R/Ls/Rs/Ltf/Rtf/Ltr/Rtr/LFE.
kLayout7_1_ch = 5, // L/C/R/Lss/Rss/Lrs/Rrs/LFE.
kLayout7_1_2_ch = 6, // L/C/R/Lss/Rss/Lrs/Rrs/Ltf/Rtf/LFE.
kLayout7_1_4_ch = 7, // L/C/R/Lss/Rss/Lrs/Rrs/Ltf/Rtf/Ltb/Rtb/LFE.
kLayout3_1_2_ch = 8, // L/C/R//Ltf/Rtf/LFE.
kLayoutBinaural = 9, // L/R.
kLayoutReserved10 = 10,
// ... kLayoutReserved11 to kLayoutReserved14 ...
kLayoutExpanded = 15,
Source: AOMediaCodec/iamf-tools, iamf/obu/audio_element.h.
Value 15, kLayoutExpanded, opens a second table with 20 entries for layouts the 4-bit field cannot hold, including 9.1.6, 22.2 and, in v2.0.0, 7.1.5.4 with bottom speakers. A coupled_substream_count tells the codec how many substreams are stereo pairs, which Opus and AAC code more efficiently than two mono channels.
Scene-based elements: Ambisonics
Scene-based elements carry Ambisonics, the full-sphere format explained in Ambisonics explained. An order-n scene has (n+1)² channels, and IAMF accepts orders 0 to 14, so up to 225 channels.
class AmbisonicsConfig() {
leb128() ambisonics_mode;
if (ambisonics_mode == MONO) {
AmbisonicsMonoConfig ambisonics_mono_config;
}
else if (ambisonics_mode == PROJECTION) {
AmbisonicsProjectionConfig ambisonics_projection_config;
}
}
class AmbisonicsMonoConfig() {
unsigned int (8) output_channel_count; // C
unsigned int (8) substream_count; // N
unsigned int (8 x C) channel_mapping;
}
class AmbisonicsProjectionConfig() {
unsigned int (8) output_channel_count; // C
unsigned int (8) substream_count; // N
unsigned int (8) coupled_substream_count; // M
signed int (16 x (N + M) x C) demixing_matrix;
}
Source: AOMediaCodec/iamf, index.bs, Ambisonics Config syntax.
MONO mode codes each Ambisonics channel as its own substream, and channel_mapping can mark channels as silent so they cost nothing. PROJECTION mode codes a matrixed version of the scene and sends the demixing_matrix that turns it back into Ambisonics. Both designs follow RFC 8486, the Opus Ambisonics specification. LPCM streams must use MONO mode.
Object-based elements (v2.0.0)
Object audio is the biggest change in v2.0.0. Its configuration is short because the interesting part, the position, travels in parameter blocks:
class ObjectsConfig() {
leb128() objects_config_size;
unsigned int (8) num_objects;
unsigned int (8 x (objects_config_size - 1)) objects_config_extension_bytes;
}
Source: AOMediaCodec/iamf, index.bs, Objects Config syntax.
num_objects is 1 for a mono substream or 2 for a stereo substream that carries two objects. Positions come from six new parameter definitions: polar, Cartesian at 8 and 16 bits, and dual versions of each for two-object substreams. The polar default looks like this:
class PolarParamDefinition() extends ParamDefinition() {
signed int (9) default_azimuth;
signed int (8) default_elevation;
unsigned int (7) default_distance;
}
Source: AOMediaCodec/iamf, index.bs, Polar Param Definition syntax.
Azimuth is clipped to plus or minus 180 degrees, elevation to plus or minus 90, and distance is a 7-bit value scaled by 127. This is a narrower object model than Dolby Atmos, which allows up to 118 objects in a cinema mix, but it closes the most common gap people raised about IAMF 1.x.
Parameters and animation
Anything that changes over time, from a fade to a moving object, is sent as a Parameter Block OBU. The Audio Element or Mix Presentation declares a parameter definition with an ID, and blocks with the same ID update it:
class ParameterBlockOBU() {
leb128() parameter_id;
(param_definition_type, param_definition_mode,
duration, num_subblocks, constant_subblock_duration,
subblock_duration)
= get_param_definition(parameter_id);
if (param_definition_mode) {
leb128() duration;
leb128() constant_subblock_duration;
if (constant_subblock_duration == 0) {
leb128() num_subblocks;
// ... subblock durations and parameter data follow in the spec ...
Source: AOMediaCodec/iamf, index.bs, Parameter Block OBU syntax (truncated).
Each block is divided into subblocks, and each subblock carries an animated value. Five animation shapes are defined, from a simple step to Bezier curves:
class AnimatedParameterData(animation_type) {
if (animation_type == STEP) {
T start_point_value;
}
if (animation_type == LINEAR) {
T start_point_value;
T end_point_value;
}
if (animation_type == BEZIER) {
T start_point_value;
T end_point_value;
T control_point_value;
unsigned int (8) control_point_relative_time;
}
if (animation_type == INTER_LINEAR) {
T end_point_value;
}
if (animation_type == INTER_BEZIER) {
T end_point_value;
T control_point_value;
unsigned int (8) control_point_relative_time;
}
}
Source: AOMediaCodec/iamf, index.bs, Animated Parameter Data syntax.
The INTER types, added in v2.0.0, drop the start value and continue from where the previous subblock ended, which saves bits on long automation curves. The same template is reused for mix gains and object positions, where T becomes a 9-bit azimuth or an 8-bit elevation. Version 2.0.0 also lets one parameter block span several frames and lets an encoder omit blocks whose values do not change.
Demixing and reconstruction gain
When a scalable channel element is played on a smaller layout, the decoder simply uses the lower layers. When it is played on the full layout, it must rebuild the surround and height channels from the downmixed lower layers plus the extra channels in the higher ones. The weights for that step come from the demixing parameter:
class DemixingInfoParameterData() {
unsigned int (3) dmixp_mode;
unsigned int (5) reserved_for_future_use;
}
Source: AOMediaCodec/iamf, index.bs, Demixing Info Parameter Data syntax.
Three bits select one of six preset combinations of five weights, written α, β, γ, δ and w_idx_offset. Mode 1, for example, uses (1, 1, 0.707, 0.707, -1). The spec applies these weights in fixed steps, such as 7.1 surrounds to 5.1 surrounds and top four to top two, so the encoder can change modes between scenes as the mix changes.
Lossy codecs smear energy between channels, so a straight demix can sound wrong. Reconstruction gain fixes that: the encoder measures the error per channel and sends a gain per channel to correct it. Recon gain is only used with Opus and AAC-LC. With FLAC or LPCM, demixing is exact and recon gain is not needed.
Mix presentations, rendering and loudness
A Mix Presentation is the finished product a listener chooses. It lists which audio elements play, at what gain, how each is rendered and how loud the result is. A stream can carry several, for example a full mix and a dialogue-forward mix.
Each element in a sub-mix carries a rendering config:
class RenderingConfig() {
unsigned int (2) headphones_rendering_mode;
unsigned int (1) element_gain_offset_flag;
unsigned int (2) binaural_filter_profile;
unsigned int (3) reserved_for_future_use;
leb128() rendering_config_extension_size;
leb128() num_parameters;
for (i = 0; i < num_parameters; i++) {
leb128() param_definition_type;
if (param_definition_type == PARAMETER_DEFINITION_POLAR) {
PolarParamDefinition polar;
} else if (param_definition_type == PARAMETER_DEFINITION_CART8) {
Cart8ParamDefinition cart8;
} else if (param_definition_type == PARAMETER_DEFINITION_CART16) {
Cart16ParamDefinition cart16;
// ... dual polar and dual Cartesian branches follow in the spec ...
Source: AOMediaCodec/iamf, index.bs, Rendering Config syntax (truncated).
headphones_rendering_mode is the field that decides what a headphone listener hears. Mode 0 renders channel-based audio to plain stereo and scenes or objects to world-locked binaural. Mode 1 is world-locked binaural for everything. Mode 2 is head-locked binaural. Mode 3 is reserved. The rendering itself is defined in a companion document, the Open Audio Renderer (OAR) specification, so every compliant player produces the same result.
Each sub-mix also lists the layouts it was checked on, with loudness information for each. Integrated loudness and peaks are stored in Q7.8 fixed point, and optional bits add true peak, anchored loudness for dialogue and, in v2.0.0, momentary loudness. Players use these values to match levels across tracks without measuring the audio themselves. For a practical view of loudness targets, see how to make YouTube videos sound cinematic.
Layouts and sound systems
Output layouts use the ITU-R BS.2051 naming, where 4+7+0 means four upper speakers, seven middle and none below. libiamf exposes them as an enum:
typedef enum IAMF_SoundSystem {
SOUND_SYSTEM_INVALID = -1,
SOUND_SYSTEM_NONE = SOUND_SYSTEM_INVALID,
SOUND_SYSTEM_A, // 0+2+0, 0
SOUND_SYSTEM_B, // 0+5+0, 1
SOUND_SYSTEM_C, // 2+5+0, 1
SOUND_SYSTEM_D, // 4+5+0, 1
SOUND_SYSTEM_E, // 4+5+1, 1
SOUND_SYSTEM_F, // 3+7+0, 2
SOUND_SYSTEM_G, // 4+9+0, 1
SOUND_SYSTEM_H, // 9+10+3, 2
SOUND_SYSTEM_I, // 0+7+0, 1
SOUND_SYSTEM_J, // 4+7+0, 1
SOUND_SYSTEM_EXT_712, // 2+7+0, 1
SOUND_SYSTEM_EXT_312, // 2+3+0, 1
SOUND_SYSTEM_MONO, // 0+1+0, 0
SOUND_SYSTEM_EXT_916, // 6+9+0, 1
SOUND_SYSTEM_EXT_7154, // 5+7+4, 1
SOUND_SYSTEM_END
} IAMF_SoundSystem;
Source: AOMediaCodec/libiamf, code/include/IAMF_defines.h.
The second number in each comment is the LFE count. System A is stereo, B is 5.1, D is 5.1.4, J is 7.1.4 and H is the 22.2 layout used in Japanese 8K broadcasting.
MP4 carriage and the codecs string
IAMF can be stored as a raw .iamf file or inside an MP4. In MP4, the track uses an 'iamf' sample entry that holds an 'iacb' configuration box with all the descriptor OBUs:
class IASampleEntry extends AudioSampleEntry('iamf') {
IAConfigurationBox ia_configuration_box;
}
Source: AOMediaCodec/iamf, index.bs, IA Sample Entry syntax.
The channelcount and samplerate fields of the sample entry are set to 0, because one IAMF track can hold many elements at different channel counts. Parsers are told to ignore them. Each MP4 sample holds the OBUs of one temporal unit. Web players check support with a codecs string such as iamf.000.000.Opus, where the two numbers are the primary and additional profiles and the last part is Opus, mp4a.40.2, fLaC or ipcm.
Decoding with libiamf
libiamf is the reference decoder. Its README describes it as the decoder for AOM IAMF v2.0.0, and the repository also holds the conformance test vectors. The public API is plain C. These declarations are quoted from the header:
IAMF_DecoderHandle IAMF_decoder_open(void);
int IAMF_decoder_close(IAMF_DecoderHandle handle);
int IAMF_decoder_set_profile(IAMF_DecoderHandle handle, IA_Profile profile);
int IAMF_decoder_configure(IAMF_DecoderHandle handle, const uint8_t *data,
uint32_t size, uint32_t *rsize);
int IAMF_decoder_decode(IAMF_DecoderHandle handle, const uint8_t *data,
int32_t size, uint32_t *rsize, void *pcm);
int IAMF_decoder_set_mix_presentation_id(IAMF_DecoderHandle handle,
uint64_t id);
int IAMF_decoder_output_layout_set_sound_system(IAMF_DecoderHandle handle,
IAMF_SoundSystem ss);
int IAMF_decoder_output_layout_set_binaural(IAMF_DecoderHandle handle);
int IAMF_decoder_set_normalization_loudness(IAMF_DecoderHandle handle,
float loudness);
Source: AOMediaCodec/libiamf, code/include/IAMF_decoder.h. Doc comments removed.
The flow follows the bitstream. Open a handle, choose a profile, choose an output (a sound system or binaural), feed the descriptor OBUs to IAMF_decoder_configure, then pass each temporal unit to IAMF_decoder_decode, which returns the number of decoded samples. The header notes that a loudness of 0.0 is a special value that turns normalisation off, and it gives -16 LUFS as a YouTube example.
Version 2.0.0 of libiamf adds head tracking for binaural output:
int IAMF_decoder_enable_head_tracking(IAMF_DecoderHandle handle,
uint32_t enable);
int IAMF_decoder_set_head_rotation(IAMF_DecoderHandle handle, float w, float x,
float y, float z);
Source: AOMediaCodec/libiamf, code/include/IAMF_decoder.h.
The rotation is a quaternion in a right-handed frame, with X to the right, Y up and Z pointing backwards. The decoder counter-rotates an intermediate Ambisonics bed so sources stay fixed in the room as the listener turns. The header warns that rotation only affects binaural output.
Encoding with iamf-tools
iamf-tools is Google's encoder and toolkit. It offers a C++ encoder and decoder API, a command-line encoder, a probe tool and a web demo that renders IAMF files to stereo or binaural WAV in the browser. The command-line encoder reads WAV files and a text configuration:
bazel-bin/iamf/cli/encoder_main --user_metadata_filename=iamf/cli/testdata/test_000002.textproto --output_iamf_directory=.
Source: AOMediaCodec/iamf-tools, docs/iamf_encoder_main.md.
It can also read an ADM BWF master directly with --adm_filename, which is the file type covered in what is an ADM BWF file. The configuration is a protocol buffer text file. This excerpt from a test vector shows how the structures above map to readable settings:
ia_sequence_header_metadata {
primary_profile: PROFILE_VERSION_SIMPLE
additional_profile: PROFILE_VERSION_SIMPLE
}
codec_config_metadata {
codec_config_id: 200
codec_config {
codec_id: CODEC_ID_LPCM
num_samples_per_frame: 128
audio_roll_distance: 0
decoder_config_lpcm {
sample_format_flags: LPCM_LITTLE_ENDIAN
sample_size: 16
sample_rate: 16000
}
}
}
audio_element_metadata {
audio_element_id: 300
audio_element_type: AUDIO_ELEMENT_CHANNEL_BASED
reserved: 0
codec_config_id: 200
audio_substream_ids: [0]
scalable_channel_layout_config {
reserved: 0
channel_audio_layer_configs: [
{
loudspeaker_layout: LOUDSPEAKER_LAYOUT_STEREO
output_gain_is_present_flag: 0
recon_gain_is_present_flag: 0
reserved_a: 0
substream_count: 1
coupled_substream_count: 1
}
]
}
}
Source: AOMediaCodec/iamf-tools, iamf/cli/testdata/test_000002.textproto (excerpt).
Every field name matches the spec syntax quoted earlier, which makes the test vectors a good way to learn the format. For a full Atmos-to-IAMF workflow without writing configuration by hand, see how to convert a Dolby Atmos mix to Eclipsa Audio.
What changed in IAMF v2.0.0
The v2.0.0 specification was approved as a final deliverable on 24 August 2026. The main additions:
- Object-based audio elements, with one or two objects per substream.
- Six position parameter definitions: polar, Cartesian 8-bit, Cartesian 16-bit and dual versions of each.
- Two codecs per sequence, provided one is LPCM.
- A second sub-mix in a presentation, intended for system sounds.
- New layouts: 10.2.9.3 (22.2) and 7.1.5.4 with bottom speakers.
- Parameter blocks that span several frames, non-key frames and omitted blocks.
- The Metadata OBU (type 24) for ITU-T T.35 data and IAMF tags.
- Momentary loudness in the loudness info.
- Three new profiles: Base-Advanced, Advanced-1 and Advanced-2.
Common misconceptions
- "IAMF is a codec." It is a container and rendering model. The audio inside is Opus, AAC-LC, FLAC or LPCM.
- "IAMF cannot do objects." True for v1.0 and v1.1. Version 2.0.0 added object-based elements with animated positions.
- "Eclipsa Audio and IAMF are different formats." Eclipsa Audio is the consumer brand. The file is IAMF.
- "Binaural rendering depends on the player." The Open Audio Renderer specification defines it, and the mix presentation chooses the headphone mode.
- "Open source means no patents." The code is BSD licensed and AOM members grant a royalty-free patent licence. That is what makes it free to ship.
For how IAMF compares with the other open standard in this space, see Eclipsa Audio vs MPEG-H 3D Audio.
Frequently asked questions
What is IAMF?
IAMF, the Immersive Audio Model and Formats, is an open specification from the Alliance for Open Media for storing and rendering immersive audio. Eclipsa Audio is its consumer name.
Which codecs does IAMF use?
Opus, AAC-LC, FLAC and LPCM. Version 2.0.0 allows two codecs in one stream when one of them is LPCM.
Does IAMF support object-based audio?
Yes, since v2.0.0, approved in August 2026. Earlier versions support channel-based and scene-based audio only.
What is an OBU in IAMF?
An Open Bitstream Unit, the packet that carries every part of an IAMF stream. Each starts with a header holding a 5-bit type and a size.
How many channels can IAMF carry?
Up to 28 decoded channels in the Base-Enhanced and Advanced-2 profiles. Scene-based elements can describe up to 14th-order Ambisonics, 225 channels.
What is the difference between libiamf and iamf-tools?
libiamf is the reference decoder written in C. iamf-tools is a C++ toolkit with an encoder, a decoder, a probe tool and a web demo.
Can I play IAMF files in a browser?
Yes. The iamf-tools web demo renders IAMF to stereo or binaural audio, and browsers that support the codecs string can play IAMF in MP4.
Is IAMF royalty-free?
Yes. The software is under the BSD 3-Clause Clear License and the AOM Patent License 1.0 grants royalty-free patent rights.
References
- IAMF specification source, index.bs, AOMediaCodec/iamf, edited by SungHee Hwang (Samsung) and Felicia Lim (Google). All spec syntax in this article.
- Immersive Audio Model and Formats, Alliance for Open Media. Published versions of the specification.
- Open Audio Renderer (OAR) 1.0.0, Alliance for Open Media. Rendering to speakers and binaural.
- iamf-tools, AOMediaCodec. Encoder, decoder, probe tool and test vectors.
- libiamf, AOMediaCodec. Reference decoder and conformance vectors.
- iamf-tools web demo, AOMediaCodec. Browser rendering to stereo and binaural WAV.
- RFC 8486, Ambisonics in an Ogg Opus Container, IETF, 2018. Basis for the mono and projection Ambisonics modes.
- ITU-R BS.2051, Advanced sound system for programme production, International Telecommunication Union. The sound system naming used for IAMF layouts.
- Introducing Eclipsa Audio: immersive audio for everyone, Google Open Source Blog, January 2025.
Turn your mixes into IAMF without writing a textproto
The reference tools are excellent for learning and testing, but most engineers do not want to hand-write configuration files for every release. Spatial9 produces Eclipsa Audio (IAMF) files from stereo songs, stems or an Atmos ADM master, with the profiles, loudness data and binaural settings handled for you. See how it works for engineers.
Code excerpts are copyright the Alliance for Open Media and are quoted under the BSD 3-Clause Clear License. Eclipsa Audio, Dolby Atmos and all other names are trademarks of their respective owners. Spatial9 is not affiliated with or endorsed by the Alliance for Open Media or Google.