The Dolby TrueHD / E-AC-3 (Atmos) / DTS:X / Eclipsa decoder for the Omniphony renderer.
In plain terms: this is the piece that lets Omniphony (and mpv-omniphony) actually play a TrueHD or Atmos soundtrack with real 3D object positioning, instead of the flat stereo/5.1 downmix you normally get. It turns the encoded audio in your movie file into sound + the spatial information the renderer needs to place each object in space.
It comes as plugins, one per codec family: you don't run them on their own. You install Omniphony or mpv-omniphony, drop the three files next to it (or tell the config where they are), and that's the whole job.
Looking for the command-line converter instead? This repo also ships
harletty— a fork oftruehddby Rainbaby, extended with E-AC-3 JOC, DTS and Auro-3D input — which turns those bitstreams into Dolby Atmos master files on disk. That one you do run yourself; see docs/harletty-cli.md for the command reference and the provenance in detail.
mpv-omniphony decoding a TrueHD Atmos track through this bridge, with Omniphony Studio attached for live object visualization.
You need two minutes and a working Omniphony or mpv-omniphony install. You do not need to compile anything — grab the prebuilt file for your system from the releases page and follow the three steps for your OS below.
Each release has one bridge archive per system,
harletty-bridge-<version>-<system>.zip. It holds three plugins, one per codec family; install all three side by side (theharletty-cli-*archives alongside are the separate offline tool — not needed for playback):
Your system Files in the archive Windows harletty_dolby_bridge.dll,harletty_dts_bridge.dll,harletty_iamf_bridge.dllLinux libharletty_dolby_bridge.so,libharletty_dts_bridge.so,libharletty_iamf_bridge.somacOS libharletty_dolby_bridge.dylib,libharletty_dts_bridge.dylib,libharletty_iamf_bridge.dylib
dolbydecodes TrueHD and E-AC-3 / AC-3 (with Atmos objects),dtsdecodes DTS, DTS-HD, DTS:X and Auro-3D,iamfdecodes IAMF (Eclipsa Audio). The host loads them together and hands each stream to the one that recognises it. On Linux the IAMF plugin uses the system's libopus (libopus0on Debian/Ubuntu,opuson Arch and Fedora); on Windows and macOS it is built in.Upgrading from a single
harletty_bridgefile? Delete it and put the three files in its place. A config whosebridge_pathstill nameslibharletty_bridge.so/harletty_bridge.dll/libharletty_bridge.dylibkeeps working: the host loads the three family plugins from that folder instead, and the next Save writes them.
Match the bridges to your Omniphony. A bridge loads only in an Omniphony built against the same
bridge_apiminor version, so each release says at the top of its notes which Omniphony it requires ("Requires Omniphony ≥ …"). The currentmainrequires Omniphony withbridge_api0.6 that loads several bridges: Omniphonymainsince mgth/Omniphony#785, and the first Omniphony release after 0.6.0. Older hosts refuse the plugins. When a host loads a plugin, its log shows what it was built from, e.g.harletty-dolby-bridge 0.9.0 (bridge_api 0.6.0, Omniphony c821eb34…).
Download harletty-bridge-<version>-windows-x86_64.zip from the
releases page and extract the three .dll files, then pick one of
the two options below.
Option A — drop them next to orender.exe (recommended, no config).
The host automatically loads every *_bridge.dll sitting in its own
folder, so there's nothing else to set up.
- Find that folder: right-click your Omniphony / mpv-omniphony
shortcut → Open file location; or search
orender.exein the Start menu, right-click the result → Open file location. - Drop the three
harletty_*_bridge.dllfiles into that folder. Keep the file names as they are (auto-detection needs the_bridge.dllending).
Done — skip to "Check it worked" below.
Option B — keep them in a folder of your choice (needs a config entry).
-
Put the files somewhere permanent, e.g. create
C:\Omniphony\and drop them in →C:\Omniphony\harletty_dolby_bridge.dlland so on. -
Tell Omniphony where they are. Open (or create) the file
%APPDATA%\omniphony\config.yaml— paste that into the address bar of Explorer to find the folder — and make sure it lists the full path to each file:render: bridge_paths: - C:\Omniphony\harletty_dolby_bridge.dll - C:\Omniphony\harletty_dts_bridge.dll - C:\Omniphony\harletty_iamf_bridge.dll
That's it. Start mpv-omniphony or Omniphony Studio and your Atmos tracks now render in 3D.
Play any TrueHD/Atmos file:
-
mpv —
mpv --ad=orender --ad-orender-osc input.mkv
--ad=orenderis what switches mpv over to object rendering for this file. It's opt-in: without it, mpv plays the track normally (FFmpeg downmix) and the bridge is never used — so if you hear sound but it's flat, you forgot this flag.--ad-orender-oscforces the OSC broadcast on for this run so Studio can attach (otherwise OSC followsrender.oscin the config). It's optional for plain playback, but required the first time you set things up through Studio — that's how Studio sees the stream and the live 3D view. -
CLI —
orender --input film.mlp ... -
Studio — start it and it shows the objects move in 3D as soon as an OSC-enabled host (mpv with
--ad-orender-osc, or the CLI) is playing.
If a bridge isn't found, the host falls back to the normal
(non-object) audio and the config save log / Studio status will say so
— double-check that bridge_paths points at the files you downloaded.
-
Download
harletty-bridge-<version>-linux-x86_64.zipfrom the releases page, and install libopus if it isn't already (sudo apt install libopus0,sudo pacman -S opus,sudo dnf install opus): the IAMF plugin uses it. -
Extract the three
.sofiles somewhere permanent, e.g.~/.local/lib/harletty/. -
Edit
~/.config/omniphony/config.yaml(create it if missing) so it contains:render: bridge_paths: - /home/you/.local/lib/harletty/libharletty_dolby_bridge.so - /home/you/.local/lib/harletty/libharletty_dts_bridge.so - /home/you/.local/lib/harletty/libharletty_iamf_bridge.so
(Or drop the files next to the
orenderbinary and skip this step — the host auto-loads every*_bridge.soin its own folder.)
Then verify it with the Check it worked steps above.
Arch users: there's nothing to download by hand — the bridge is on the AUR as
harletty-bridge:paru -S harletty-bridgeIt builds from this repo's release and installs the plugins in
/usr/lib/orender/, the system plugin folder the host searches last when no bridge is configured. A config that names them explicitly:render: bridge_paths: - /usr/lib/orender/libharletty_dolby_bridge.so - /usr/lib/orender/libharletty_dts_bridge.so - /usr/lib/orender/libharletty_iamf_bridge.so
-
Download
harletty-bridge-<version>-macos-arm64.zipfrom the releases page. -
Extract the three
.dylibfiles somewhere permanent, e.g.~/Library/Application Support/omniphony/. -
Edit
~/.config/omniphony/config.yaml(create it if missing) so it contains:render: bridge_paths: - /Users/you/Library/Application Support/omniphony/libharletty_dolby_bridge.dylib - /Users/you/Library/Application Support/omniphony/libharletty_dts_bridge.dylib - /Users/you/Library/Application Support/omniphony/libharletty_iamf_bridge.dylib
(Or drop the files next to the
orenderbinary and skip this step — the host auto-loads every*_bridge.dylibin its own folder.)
Then verify it with the Check it worked steps above.
harletty-bridge decodes raw or IEC61937-wrapped access units into PCM
plus OAMD spatial metadata and hands them to liborender over a stable
abi_stable ABI
(bridge_api).
The renderer side is format-agnostic on purpose: harletty-bridge is
the only piece in the stack that names the specific input formats. Plug
in a different bridge and liborender will happily render whatever else
feeds it OAMD-shaped metadata.
Step by step:
- Receives raw TrueHD or E-AC-3 (JOC) access units from the host
(the
orenderCLI, orad_orenderinside mpv), either as raw payload or as IEC61937 frames (PipeWire encoded sinks). - Decodes them with the bundled
truehd/eac3crates. - Parses OAMD metadata into the renderer-neutral shape described by
bridge_api(per-object positions, gains, sizes, channel labels, …) and emits PCM in parallel. - The renderer takes care of VBAP, distance / spread modeling and the speaker-side output.
Architecturally each bridge is a runtime dlopen plugin — the exact
same loading pattern Omniphony uses for any format-specific bridge
(*_bridge.so / .dll / .dylib). harletty ships one per codec
family; the host loads them together, asks each one's probe where a
stream of its family starts, and routes each stream to the one that
claims it
(docs/multi-bridge.md
in Omniphony).
| Repo | Role |
|---|---|
Omniphony |
The renderer (liborender C library, orender CLI, omniphony-studio 3D supervision UI). Loads this bridge at runtime. |
mpv-omniphony |
mpv patched with the ad_orender audio decoder; embeds liborender so mpv plays Atmos with full object rendering instead of FFmpeg's downmix. |
Only needed if you want to hack on the bridge or there's no prebuilt
artifact for your platform. Requires a Rust toolchain and a checkout of
Omniphony next to this repository, as
../Omniphony: the bridge crates take bridge_api (and, for the
bench, spdif) from it by path. .omniphony-ref names the
Omniphony commit CI and the releases build against; check that one out to
build what a release would:
git clone https://github.com/harletty/harletty-bridge
git clone https://github.com/mgth/Omniphony
git -C Omniphony checkout "$(harletty-bridge/scripts/omniphony-ref.sh)"
cd harletty-bridgeThen:
./build_bridge.sh # Linux / macOS / MSYS — builds the three pluginsbuild_bridge.bat :: Windows nativeThe build produces target/release/libharletty_{dolby,dts,iamf}_bridge.{so,dylib}
on unix and target\release\harletty_{dolby,dts,iamf}_bridge.dll on
Windows. Point bridge_paths at those files exactly as in the install
steps above.
The IAMF plugin links libopus. On Linux it takes the system's through
pkg-config (install libopus-dev or your distribution's equivalent). On
Windows and macOS, build_bridge builds libopus from source first and
links it in statically (scripts/build-static-opus.sh, CMake needed), as
the release does; set OPUS_LIB_DIR (and OPUS_STATIC=1) to use one of
your own instead.
Each library records the Omniphony commit it was built against (git rev-parse HEAD in ../Omniphony, or HARLETTY_OMNIPHONY_COMMIT when set,
unknown when neither is available) and logs it with its own version and
the bridge_api version when a host loads it.
Each codec family is a crate of its own, and each has a plugin crate that makes it a bridge library:
| Plugin (package) | Library | Decodes | IEC 61937 burst types |
|---|---|---|---|
harletty-dolby-bridge |
harletty_dolby_bridge |
TrueHD (raw and MAT), E-AC-3 / AC-3 with JOC objects | 0x01, 0x15, 0x16 |
harletty-dts-bridge |
harletty_dts_bridge |
DTS, DTS-HD, DTS:X, Auro-3D over DTS-HD MA | 0x0B–0x0D, 0x11 |
harletty-iamf-bridge |
harletty_iamf_bridge |
IAMF (Eclipsa Audio) | 0x1D (private, see below) |
IAMF has no IEC 61937 data type of its own. 0x1D is a private one, for a
sender and a receiver that are both Omniphony's: a burst carries the OBUs of
one temporal unit, with a redundant copy of the descriptors now and then
(Omniphony docs/iamf-transport-plan.md). No other equipment decodes it.
Build any of them on its own, e.g. cargo build --release -p harletty-iamf-bridge. A plugin's crate graph holds its own family's
decoders and no other's, which CI checks
(scripts/check-crate-isolation.sh).
bridge/ is the combined bridge, every family behind one router, kept as
a library for the fuzz target, bridge_bench and the bit-exactness kit;
it is no longer shipped. Its Cargo features (dolby, dts, iamf) pick
the families it holds.
harletty is a fork of truehdd
by Rainbaby — 85% of the shared-lineage
code is byte-identical to upstream, including the whole DAMF master-set
writer and the CAF/Wave64 writers. What was added here is E-AC-3 JOC and
DTS/DTS:X input, the latter exported as ADM — on a DTS-HD MA carrier and on
a lossy DTS-HD HRA one (docs/dtsx-lossy-carrier.md) —
the unfolding of Auro-3D carriers (a DTS-HD MA track whose low bits
hold a folded 9.1 to 13.1 layout) into their bed and height layer, and
IAMF input (v2.0 objects and v1.1 beds, in builds with the iamf feature).
It turns those bitstreams into Dolby Atmos master files (.atmos,
.atmos.metadata, plus CAF/WAV audio), reading a file or stdin, so it
pipes straight out of ffmpeg:
ffmpeg -i movie.mkv -map 0:a:0 -c copy -f truehd - \
| harletty --progress decode - --output-path "movie"📖 docs/harletty-cli.md — commands, every option, output files, recipes and limitations.
Grab harletty-cli-<version>-<system>.zip from the
releases page,
or build it with cargo build --release -p harletty (add --features iamf
for IAMF input, which links the system libopus).
The rename is not a claim of authorship — it exists because the binary
accepts a superset of upstream's inputs and writes labels upstream does
not, so two binaries called truehdd on one PATH would be a miserable
thing to debug. See
docs/truehdd-fork-retirement.md for
the audit of what was ported, superseded or imported.
It is also a separate artifact: the bridges do not link it, do not
pay for it, and cannot reach it. That isolation is by crate graph rather
than feature flags, and scripts/check-crate-isolation.sh asserts it in
CI — if you find yourself wanting use damf::… inside a bridge crate,
the mapping you want belongs on the CLI side instead.
The repo is a virtual cargo workspace — no package at the root. It builds the bridge plugins and the CLI from one decoder lineage.
plugin-dolby/ # the Dolby bridge plugin (harletty_dolby_bridge)
plugin-dts/ # the DTS bridge plugin (harletty_dts_bridge)
plugin-iamf/ # the IAMF bridge plugin (harletty_iamf_bridge)
bridge-common/ # what the families share: FamilyPipeline, PluginBridge, probe helpers, root module
bridge/ # the combined bridge (every family behind one router), for fuzz / bench / kit
bridge-family-dolby/ # TrueHD (raw / MAT) and E-AC-3 / AC-3 JOC paths
bridge-family-dts/ # DTS, DTS-HD, DTS:X and Auro-3D paths
bridge-family-iamf/ # IAMF path (iamf-rs)
harletty/ # offline CLI: decode/info, codec probing, codec->OAMD
damf/ # DAMF metadata + CAF/WAV writers (CLI-side only)
truehdd-macros/ # proc macros used by the CAF writer and `info`
truehd/ # TrueHD decoder crate (vendored, Apache-2.0)
eac3/ # E-AC-3 (JOC) decoder crate
dca/ # DTS (core / XXCH / DTS-HD MA / XLL / DTS:X) decoder crate
auro/ # Auro-Codec side channel: detection and unfold
docs/ # protocol notes (IEC61937, OAMD shape, …)
tools/host-baseline/ # bit-exactness through Omniphony's own bridge router
EAC3_PATCH_NOTES.md # upstream patches to the E-AC-3 decoder
OBJECT_SIZE_NOTES.md # notes on OAMD object_size handling
.omniphony-ref # the Omniphony commit CI and releases build against
.github/workflows/ # CI on pushes and pull requests; release builds on `v*` tags
The hard part of this project — actually decoding a TrueHD bitstream — is not our work, and neither is most of the offline CLI. Both come from truehdd by Rainbaby, a clean-room Rust parser and decoder for Dolby TrueHD.
Concretely, what is Rainbaby's:
truehd/— the TrueHD parser and decoder, vendored essentially unchanged. Without it this bridge would have nothing to hand to the renderer.- The
harlettyCLI, which is a fork of upstream's own binary rather than something built alongside it. Of the 5470 lines with a shared lineage, 4655 (85%) are byte-identical to upstream: the command structure, the DAMF master-set writer, the CAF and Wave64 writers, theinforeport andtruehdd-macros/are all upstream work.
What is ours: the bridge_api ABI wrapper and the OAMD plumbing the
renderer needs; the eac3, dca and auro crates; the E-AC-3 JOC,
DTS/DTS:X and Auro-3D routing in the CLI; and decoder robustness fixes. Upstream is Apache-2.0,
as is this repo.
So: huge thanks to Rainbaby and the truehdd project. If any of this
is useful to you, go star truehdd.
That is where the hard part was done.
The sources in this repository are Apache-2.0. The two things it builds are not distributable under the same terms, so they are worth separating:
harletty, the CLI — Apache-2.0. It depends only on this workspace, on crates.io and on our fork oftruehd(truehdandtruehdd-macrosare Apache-2.0 too), so the binary carries no copyleft.libharletty_{dolby,dts,iamf}_bridge.so/.dll, the decoder bridges — GPL-3.0-or-later. It linksbridge_apifrom Omniphony, which is GPL-3.0-or-later, and the resulting library is a combined work. Apache-2.0 code may be combined into a GPLv3 work, so there is no licence conflict — but what you receive is governed by the GPL, and linking it into a proprietary program is not permitted. Source for both halves is public.
Note that this is one-way: Apache-2.0 is incompatible with GPLv2 because of
its patent clause, so anything built on truehd can be GPLv3 but never GPLv2.
The TrueHD decoder itself is the truehd crate (Apache-2.0), © its original
author; it used to be vendored here and no longer is. It is built from
our fork, pinned in the root
Cargo.toml, which carries a decode-speed series on top of upstream.
