Skip to content
HN On Hacker News ↗

GitHub - raress96/dolby-atmos-encoder: PoC Dolby Atmos encoder

▲ 26 points 7 comments by xbmcuser 4w ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is a mix of AI-generated, AI-assisted, and human-written content

76 %

AI likelihood · overall

Mixed
19% human-written 72% AI-generated
SEGMENTS · HUMAN 0 of 7
SEGMENTS · AI 6 of 7
WORD COUNT 1,704
PEAK AI % 99% · §1
Analyzed
Jun 14
backend: pangram/v3.3
Segments scanned
7 windows
avg 243 words each
Distribution
19 / 72%
human / AI fraction
Verdict
Mixed
Pangram v3.3

Article text · 1,704 words · 7 segments analyzed

Human AI-generated
§1 AI · 99%

Convert a Dolby Atmos Master (DAMF) — as decoded from a Dolby TrueHD + Atmos stream by truehdd — into E-AC-3 (Dolby Digital Plus) with Joint Object Coding (JOC), i.e. "DD+ Atmos". The aim was to let consumer gear that can't bitstream TrueHD Atmos (e.g. an LG TV → Denon AVR over eARC) render real object-based Atmos with height.

Status: research-complete, hardware-blocked Every stage below is provably correct by every software oracle available (ffmpeg 7, VoidXH/Cavern): the output is detected as E-AC-3 (Dolby Digital Plus + Dolby Atmos), decodes with the right object count and valid 3D object positions (including height), and is CRC-clean. But it does not engage Atmos on Dolby-certified hardware — playback falls back to Dolby Surround. The cause is two independent, proprietary walls (see Why it can't fully work). This repository is the honest, documented artifact of that investigation, with a clean seam for the one missing cryptographic piece.

This is for personal / interoperability research use only. See Licensing & provenance.

What it does (pipeline) A near-complete reimplementation of the relevant parts of a DD+ Atmos encoder, in Rust:

DAMF reader — parses the Dolby Atmos Master (.atmos / .audio / .metadata). 5.1 downmix renderer — VBAP-pans the Atmos objects to a 5.1 bed (L R C LFE Ls Rs). OAMD encoder (ETSI TS 103 420) — per-frame Object Audio Metadata: object positions (with elevation), bed/LFE, program assignment. Round-trips through our decoder and Cavern. JOC encoder — Joint Object Coding matrices over the 5-channel core × parameter bands, with Dolby's quant/Huffman config.

§2 AI · 96%

Bit-exact round-trip. EMDF container (ETSI TS 102 366 Annex H) — wraps OAMD (id 11) + JOC (id 14) with the emdf_protection field, carried in the E-AC-3 audio-block skip field exactly where real Dolby streams put it (recomputing frmsiz + crc2). addbsi signaling — the flag_ec3_extension_type_a + complexity_index Atmos-detection flag.

The E-AC-3 core itself is encoded externally (e.g. by ffmpeg); this tool injects/synthesizes the Atmos metadata layer. See Command variants below.

Build cargo build --release (Stand-alone crate — it does not depend on the truehdd library; it consumes the DAMF files truehdd produces. The only out-of-the-ordinary deps are hmac/sha2, used by the EMDF signing seam.) Memory use (large files) The frame pipeline streams: eac3::transform_frames_io reads the E-AC-3 core in a few-MiB rolling buffer, transforms each syncframe, and writes it straight out, so memory stays bounded regardless of file length (E-AC-3 frames are ≤4 KB, so a 4 MiB chunk batches ~1000 frames per read). eac3inject and oamd are fully streamed (input + output); atmos streams its output and still loads the core once for per-frame JOC context. Fully streaming atmos's input and the (large) master audio essence is possible future work. Note: this is plain synchronous streaming — async would add a runtime without helping a single-file, CPU-bound batch transform. Test files / samples You need a source that actually contains Dolby TrueHD + Atmos. Public sample sources:

Kodi samples wiki — https://kodi.wiki/view/Samples (see the Dolby TrueHD / Atmos entries). r/hometheater — "surround sound test files in almost every format" — https://www.reddit.com/r/hometheater/comments/11qqv95/surround_sound_test_files_in_almost_every_format/ (links to TrueHD Atmos, DD+ JOC Atmos and more).

§3 AI · 96%

A TrueHD Atmos clip is the lossless source you decode to a DAMF master; a known-good DD+ JOC Atmos clip is handy as a reference for jocprobe / emdfverify. Toolchain: from a Blu-ray MKV to a testable stream Tools: truehdd, ffmpeg (≥5), mkvextract / mkvmerge (MKVToolNix), this crate, and optionally Cavern (verification, below). # 0. Build this tool. cargo build --release # -> target/release/dolby-atmos-encoder

# 1. Extract the TrueHD elementary stream from the MKV. # Find the TrueHD track first: ffprobe movie.mkv ffmpeg -i movie.mkv -map 0:a:0 -c copy -f truehd movie.thd # (or, by track id: mkvextract tracks movie.mkv 1:movie.thd)

# 2. Decode TrueHD -> Dolby Atmos Master (DAMF). --presentation 3 selects the Atmos presentation. truehdd decode --presentation 3 movie.thd --output-path master # -> master.atmos + master.atmos.audio + master.atmos.metadata

# 3. Render objects to a 5.1 bed, then encode an E-AC-3 core with ffmpeg. # Pipe raw f32le straight into ffmpeg — works for any length (no WAV size cap): dolby-atmos-encoder downmix master.atmos --out - \ | ffmpeg -f f32le -ar 48000 -ac 6 -i - -c:a eac3 -b:a 768k -f eac3 core.eac3 # (Short clips only — `--out downmix.wav` then `ffmpeg -i downmix.wav ...` — but a 5.1 # 32-bit-float WAV is capped at 4 GB, i.e. ~58 min at 48 kHz, so prefer the pipe above.)

# 4. Inject per-frame Atmos metadata (OAMD + JOC + the addbsi detection flag) into the core.

§4 Mixed · 65%

dolby-atmos-encoder atmos core.eac3 master.atmos --out atmos.eac3 # (add --emdf-key <hex> to sign the EMDF protection field — see "The signing seam")

# 5. Mux into an MKV alongside your video. mkvmerge -o out.mkv --no-audio movie.mkv atmos.eac3

# 6. Quick check (full verification with Cavern below). ffmpeg -i atmos.eac3 # expect: Audio: eac3 (... Dolby Atmos ...)

⚠ This yields a stream ffmpeg/Cavern accept as Atmos, but it will not engage Atmos on Dolby-certified hardware — see Why it can't fully work.

Subcommands Run --help on each for details.

Command Purpose

inspect <atmos> Report a DAMF master's bed/object layout and metadata.

downmix <atmos> --out d.wav Render objects to a 5.1 bed WAV (--out - streams raw f32le to stdout — no 4 GB cap, pipe into ffmpeg).

atmos <core> <atmos> --out o.eac3 Inject OAMD+JOC metadata + the addbsi flag into an E-AC-3 core.

oamd <core> <atmos> --out o.eac3 OAMD only (no JOC).

coregraft <realcore> <myatmos> --out o.eac3 Splice our metadata onto a real Dolby core (diagnostic).

graft <core> <reference> --out o.eac3 Splice Dolby's metadata onto our core (diagnostic).

jocprobe / eac3probe / walkprobe / bsidump Stream inspectors.

oamddump <hex> Verbose field-by-field OAMD decode.

emdfverify <input> Check whether our protection CRC matches a stream's stored emdf_protection.

Global: --emdf-key <hex|@file> and --emdf-key-id <0..7> (or DOLBY_EMDF_KEY) — see The signing seam.

§5 AI · 94%

Verifying with Cavern Cavern is the open-source decoder we validate against: if Cavern reports objects, the OAMD/JOC metadata is well-formed. A small harness is included in tools/cavernprobe: # Needs the .NET 8 SDK and a local Cavern checkout (adjust the path in cavernprobe.csproj). dotnet build tools/cavernprobe -c Release dotnet tools/cavernprobe/bin/Release/net8.0/cavernprobe.dll atmos.eac3 It prints the channel layout, HasObjects, and the decoder's full metadata (object_count, joc_num_objects, object positions). On a stream from this tool: channels : 6 HasObjects : True [JOC information] joc_num_objects (Number of rendered dynamic objects): ... ... RESULT: object-based audio detected (Atmos objects present).

Exit code 0 = objects present, 2 = channel-based only. (ffmpeg ≥5's eac3 decoder also reports + Dolby Atmos as a faster first-pass sanity check.) Results / verified on Run end-to-end on a full feature film — a Logan (2017) 2160p UHD Blu-ray remux, TrueHD 7.1 + Atmos (137 min) — on a single WSL2 workstation:

Stage Result

truehdd decode 9,891,890 TrueHD frames → 395,675,600 samples (full 137 min), 16 GB DAMF essence

downmix --out - → ffmpeg 791 MB E-AC-3 core @ 768 kbps (raw-pipe path;

§6 AI · 82%

no WAV size cap hit)

atmos inject 257,602 E-AC-3 frames; 13 dynamic objects + LFE; JOC avg 213 B EMDF/frame

Carriage skip-field on 257,602 / 257,602 frames (100%); addbsi detection flag on all

Output 809 MB atmos.eac3

Cavern HasObjects=True, 13 objects, 1 bed instance, 5-channel JOC downmix

Memory is bounded by frame count, not file size. Every subcommand streams the core through eac3::transform_frames_io (~4 MiB rolling buffer), so the working set is fixed regardless of length: that same 791 MB Logan core runs eac3inject at 7.9 MB peak RSS (a 1.25 GB synthetic core, 487,424 frames, also holds ~7.9 MB). atmos builds its JOC context from a streamed header-only scan (eac3::parse_frames_io) of the core plus a streaming pass over the CAF essence — both O(number of frames), never the core bytes — then streams the input core and output through the same path. So it no longer holds the core in RAM: an earlier build peaked at 815 MB on Logan (it loaded the whole core); it now stays in the tens of MB (≈ the 7.9 MB streaming floor plus the per-frame position/power tables). Output is byte-identical across this refactor (golden SHA-256 verified).

These are decoder-side results: ffmpeg and Cavern both accept the stream as object Atmos. It still will not engage Atmos on Dolby-certified hardware — see below for why.

Why it can't fully work Two diagnostic builds bisect the problem. Both pass ffmpeg 7 and Cavern; both fall back to Dolby Surround on real hardware: Wall 1 — the core encoder graft = our ffmpeg-encoded core + Dolby's genuine metadata → Dolby Surround. So the core itself is rejected even with perfect metadata.

§7 AI · 96%

ffmpeg's E-AC-3 encoder is not Dolby-grade (e.g. it does no channel coupling); there is no open-source Dolby-conformant E-AC-3 core encoder. Wall 2 — EMDF keyed authentication (the decisive one) coregraft = a real Dolby core + our metadata → Dolby Surround. The metadata is rejected too, and we traced it to the EMDF emdf_protection field, which is a keyed authentication code, not a computable checksum:

The spec says so. ETSI TS 102 366 v1.4.1 §H.2.2: key_id "identifies the authentication key used to calculate the value of the protection_bits_primary and protection_bits_secondary fields," and that calculation is "implementation dependent and is not defined in the present document." Brute force confirms it. emdfverify dumps each real frame's protection bits; an offline sweep of all 256 CRC-8 polynomials × every init/xorout/reflection over 7 candidate byte-regions across 8 real Dolby frames found ZERO matches, and no standard CRC-32 variant matched the 32-bit primary. The null result is the signature of a real secret key. No open decoder computes it. truehdd marks the field // TODO: HMAC; Cavern and ffmpeg don't validate it at all. Only Dolby's licensed encoder (holding the key) can produce valid protection bits; certified hardware validates them.

Conclusion: an open-source encoder that produces hardware-conformant DD+ JOC Atmos is not achievable. The audio coding is solved; the format gates playback behind a proprietary cryptographic signature by design.

The signing seam (--emdf-key) All of the proprietary cryptography is isolated behind one trait, emdf::EmdfProtector, with one real implementation point, emdf::dolby_keyed_mac:

PublicCrcProtector (default) — public CRC-32 / CRC-8. Well-formed but unsigned; this is the historical behaviour and what you get with no key. KeyedProtector — selected by --emdf-key <hex> (or DOLBY_EMDF_KEY).