Skip to main content

Crate rdd29

Crate rdd29 

Source
Expand description

SMPTE RDD 29:2019 Dolby Atmos® bitstream — frame/element framing plus bed/object metadata.

This crate implements exactly the wire structures described in the curated spec transcription at rdd29/docs/rdd29.md (fetched directly from https://pub.smpte.org/pub/rdd29/rdd29-2019.pdf) — cite that file, not this doc comment, as the field-semantics oracle.

  • AtmosFrame — one complete frame: the top-level ATMOSFrame element (§2.1/§4.2), containing zero or more sub-AnyElements.
  • BedDefinition1 — a channel-based audio bed’s channel-to-audio-asset mapping (§2.2/§4.3).
  • ObjectDefinition1 — one panned audio object’s per-sub-block pan/ rendering metadata: 3D position, snap, zone gain, spread, decorrelation, plus an optional text description (§2.3/§4.4).
  • AudioDataDlc — one track’s audio essence pointer + opaque payload (§2.4/§4.5).

What this crate is not: an audio codec. AudioDataDlc’s payload is the Dolby Lossless Coding (DLC) codec’s own bit-packed bitstream (linear- predictive + Rice-Golomb entropy-coded residual audio samples) — this crate treats it as opaque bytes, the same “parse the container, not the codec” discipline this workspace’s transmux/st337 crates use for media containers and AES3 non-PCM bursts respectively. See docs/rdd29.md’s “Scope decisions” for the exact boundary and the two honest gaps in the source disclosure document this crate had to resolve (the Plex(8) pseudocode’s internal inconsistency, and the completely undocumented AudioDescription field semantics).

Depends only on broadcast-common. #![no_std] (+ alloc) when the std feature is disabled.

§Examples

Build a frame with a bed and an audio-essence element, and round-trip it:

use broadcast_common::{Parse, Serialize};
use rdd29::{
    AtmosFrame, AudioDataDlc, BedChannel, BedDefinition1, BitDepth, ChannelId, FrameRate,
    SampleRate,
};

let bed = BedDefinition1::new(
    1,
    vec![BedChannel {
        channel_id: ChannelId::LeftScreen,
        audio_data_id: 10,
    }],
);
let dlc = AudioDataDlc::new(10, &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();

let frame = AtmosFrame::new(
    SampleRate::Hz48000,
    BitDepth::Bits24,
    FrameRate::Fps24,
    1,
    vec![
        rdd29::AnyElement::BedDefinition1(bed),
        rdd29::AnyElement::AudioDataDlc(dlc),
    ],
);

let bytes = frame.to_bytes();
assert_eq!(AtmosFrame::parse(&bytes).unwrap(), frame);

§Runnable examples

Run with cargo run -p rdd29 --example <name>.

§build_atmos_frame

//! Build an `ATMOSFrame` from typed bed/object/audio-essence elements, and
//! serialize it to wire bytes — SMPTE RDD 29:2019 §2/§4.
//!
//! Run with `cargo run -p rdd29 --example build_atmos_frame`.

use broadcast_common::Serialize;
use rdd29::{
    AnyElement, AtmosFrame, AudioDataDlc, AudioDescription, BedChannel, BedDefinition1, BitDepth,
    ChannelId, DecorCoefPrefix, FrameRate, ObjectDefinition1, ObjectSpreadMode, PanInfo,
    PanSubBlock, SampleRate,
};

fn main() {
    // A 2.0 bed: Left/Right screen speakers, pointing at two AudioDataDLC
    // tracks (audio_data_id 10/11).
    let bed = BedDefinition1::new(
        1,
        vec![
            BedChannel {
                channel_id: ChannelId::LeftScreen,
                audio_data_id: 10,
            },
            BedChannel {
                channel_id: ChannelId::RightScreen,
                audio_data_id: 11,
            },
        ],
    );

    // One panned object, centered in the room, snapping to the closest
    // speaker. FrameRate::Fps24 requires 8 pan sub-blocks (Table 7); only
    // sub-block 0 carries real pan info here, the rest repeat it.
    let mut pan_sub_blocks = vec![PanSubBlock {
        pan: Some(PanInfo {
            pos_x: 0x8000,
            pos_y: 0x8000,
            pos_z: 0x4000,
            snap: true,
            zone_gains: None,
            spread_mode: ObjectSpreadMode::Lowrez,
            spread: 32,
            decor_coef_prefix: DecorCoefPrefix::NoDecorrelation,
            decor_coef: None,
        }),
    }];
    pan_sub_blocks.resize(8, PanSubBlock { pan: None });
    let object = ObjectDefinition1::new(
        2,
        12,
        pan_sub_blocks,
        AudioDescription::with_text(b"footsteps").unwrap(),
    )
    .expect("build ObjectDefinition1");

    // Opaque audio-essence payloads -- this crate never decodes DLC audio,
    // so any bytes work here (see docs/rdd29.md scope decision 3).
    let left = AudioDataDlc::new(10, b"left channel DLC payload").unwrap();
    let right = AudioDataDlc::new(11, b"right channel DLC payload").unwrap();
    let object_audio = AudioDataDlc::new(12, b"object DLC payload").unwrap();

    let frame = AtmosFrame::new(
        SampleRate::Hz48000,
        BitDepth::Bits24,
        FrameRate::Fps24,
        3, // MaxRendered: 2 bed channels + 1 object
        vec![
            AnyElement::BedDefinition1(bed),
            AnyElement::ObjectDefinition1(object),
            AnyElement::AudioDataDlc(left),
            AnyElement::AudioDataDlc(right),
            AnyElement::AudioDataDlc(object_audio),
        ],
    );

    let bytes = frame.to_bytes();
    println!("serialized ATMOSFrame: {} bytes", bytes.len());
    println!(
        "version={} sample_rate={} bit_depth={} frame_rate={} max_rendered={}",
        frame.version, frame.sample_rate, frame.bit_depth, frame.frame_rate, frame.max_rendered
    );
    println!("sub-elements: {}", frame.elements.len());
}

§parse_atmos_frame

//! Wrap the committed real-fixture E-AC-3 frame
//! (`tests/fixtures/eac3_frame0.bin`) as an opaque `AudioDataDLC` payload
//! inside a hand-built `ATMOSFrame`, parse it back, and confirm the payload
//! is byte-identical to the real capture — SMPTE RDD 29:2019 §2.4/§4.5.
//!
//! Run with `cargo run -p rdd29 --example parse_atmos_frame`.

use broadcast_common::{Parse, Serialize};
use rdd29::{
    AnyElement, AtmosFrame, AudioDataDlc, BedChannel, BedDefinition1, BitDepth, ChannelId,
    FrameRate, SampleRate,
};

fn main() {
    // See st337/docs/st337-PROVENANCE.md for how this fixture was extracted
    // (a real E-AC-3 syncframe from fixtures/ts/dolby/eac3.ts). RDD 29 never
    // decodes audio essence -- this crate treats it as an opaque
    // AudioDataDLC payload, exactly like st337 treats its burst_payload.
    let path = concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/tests/fixtures/eac3_frame0.bin"
    );
    let real_audio = std::fs::read(path).expect("fixture must exist");

    let bed = BedDefinition1::new(
        1,
        vec![BedChannel {
            channel_id: ChannelId::LeftScreen,
            audio_data_id: 10,
        }],
    );
    let dlc = AudioDataDlc::new(10, &real_audio).expect("build AudioDataDLC");

    let frame = AtmosFrame::new(
        SampleRate::Hz48000,
        BitDepth::Bits24,
        FrameRate::Fps24,
        1,
        vec![
            AnyElement::BedDefinition1(bed),
            AnyElement::AudioDataDlc(dlc),
        ],
    );
    let bytes = frame.to_bytes();

    let parsed = AtmosFrame::parse(&bytes).expect("parse ATMOSFrame");
    println!("version: {}", parsed.version);
    println!("sample_rate: {}", parsed.sample_rate);
    println!("frame_rate: {}", parsed.frame_rate);
    println!("sub-elements: {}", parsed.elements.len());

    let AnyElement::AudioDataDlc(parsed_dlc) = &parsed.elements[1] else {
        panic!("expected AudioDataDLC as the second element");
    };
    assert_eq!(
        parsed_dlc.payload, real_audio,
        "byte-identical real E-AC-3 payload carried as opaque AudioDataDLC bytes"
    );
    println!(
        "AudioDataDLC payload ({} bytes) matches the real E-AC-3 fixture byte-for-byte.",
        parsed_dlc.payload.len()
    );
}

Modules§

distance
Relative distance coding — RDD 29:2019 §3.2.

Structs§

AtmosFrame
The ATMOSFrame element (§2.1/§4.2/§5.2): the entire Dolby Atmos frame.
AudioDataDlc
The AudioDataDLC element (§4.5/§5.5): one track’s audio essence, referenced by crate::BedDefinition1/crate::ObjectDefinition1 via AudioDataID.
AudioDescription
AudioDescription — RDD 29 §4.4 (syntax only; no 5.4.x prose section documents this field’s semantics — see docs/rdd29.md scope decision 2).
BedChannel
One bed channel: a ChannelId paired with the crate::AudioDataDlc AudioDataID it draws audio from (§4.3, the for(n...) loop body).
BedDefinition1
The BedDefinition1 element — RDD 29 §2.2/§4.3/§5.3: metadata and pointers to audio essence for one frame of one audio bed.
ObjectDefinition1
The ObjectDefinition1 element — RDD 29 §2.3/§4.4/§5.4: pan/rendering metadata (position, snap, zone gain, spread, decorrelation) and a pointer to audio essence, for one frame of one panned audio object.
PanInfo
One sub-block’s pan/rendering info (present when PanInfoExists == 1) — RDD 29 §4.4/§5.4.2-§5.4.11.
PanSubBlock
One NumPanSubBlocks sub-block (§4.4’s for(sb...) loop body).

Enums§

AnyElement
Any parsed Dolby Atmos bitstream element — the result of one ReadElement() dispatch (§4.1). A tag-dispatch enum (see this crate’s tests/label_coverage.rs SKIP list), not itself a spec/field label.
BitDepth
The 2-bit BitDepth field (§5.2.3 Table 3). “Only 24-bits per audio sample are currently supported” (spec’s own emphasis).
ChannelId
The 4-bit ChannelID field (§5.3.3 Table 6): the nominal loudspeaker a bed channel is assigned to.
DecorCoefPrefix
The 2-bit ObjectDecorCoefPrefix code — RDD 29 §5.4.10 Table 11.
ElementId
The ElementID field (Table 1, §5.1.1): identifies the kind and contents of a Dolby Atmos bitstream element.
Error
An RDD 29 element/frame parse or serialize error.
FrameRate
The 4-bit FrameRate field (§5.2.4 Table 4): the Dolby Atmos frame rate.
ObjectSpreadMode
The 2-bit ObjectSpreadMode code — RDD 29 §5.4.8 Table 10.
SampleRate
The 2-bit SampleRate field (§5.2.2 Table 2).
ZoneGain
The 2-bit ZoneGain code — RDD 29 §5.4.7 Table 9.
ZoneId
Zone identifiers — RDD 29 §5.4.5 Table 8. Informative: this is the array position a ZoneGain occupies in wire order, not a value that itself appears on the wire.

Constants§

ATMOS_VERSION
ATMOSVersion this crate implements (“This document describes the protocol with ATMOSVersion = 1”, §5.2.1).
ELEMENT_ID_ATMOS_FRAME
ATMOS_FRAME (Table 1, §5.1.1): the frame-header element.
ELEMENT_ID_AUDIO_DATA_DLC
AUDIO_DATA_DLC (Table 1).
ELEMENT_ID_BED_DEFINITION1
BED_DEFINITION1 (Table 1).
ELEMENT_ID_OBJECT_DEFINITION1
OBJECT_DEFINITION1 (Table 1).
MAX_KNOWN_ZONES
Number of known zones (§5.4.5 Table 8 has 9 rows, IDs 0-8).
PLEX_MAX_VALUE
Largest value a Plex-coded symbol may hold (RDD 29 §3.4: “symbols to be Plex encoded shall have a value less than or equal to 0xFFFFFFFE”).

Type Aliases§

Result
Result alias for rdd29 parsing/serialization.