Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,31 @@ the public-API contract.

## [Unreleased]

_Nothing yet._

## [7.40.0] - 2026-10-18

### Changed

- **TrueHD Atmos is built from the stream's own channel presentation.** With `objectAudioRendering`
in a `TrueHDObjectAudio` build, every output is now a share of the 7.1 (or 5.1) bed Dolby renders
the same mix into, split per frequency bin between floor and heights by where the objects' energy
lies (`BedSplitter`). No object signal is mixed into an output; the objects are decoded without
their matrix interpolation (LibTrueHD 3.0 no longer compiles the interpolating decode at all) and
taken as points at their positions, without size, snap, zones, screen anchoring, the elevation
switch or metadata ramps. Measured against the previous exact render through Apple's APAC decode on
72 windows of six films, 0.35 % of the energy lands on another speaker (median of the windows with
heights), Dolby's Unfold demo 3.3 %, the heights within about a dB. A 5.1 bed now covers a 7.1
layout's rears as well as its sides, and the split's 2048-frame latency is drained at the end of
input and before every encoder restart. The exact render it is measured against is only built with
the measurement-only `TrueHDReferenceRender` trait (#731).
- **DTS:X plays its standard presentation only, into 7.1.4 or 9.1.6.** With LibDTSX 3.0, the heights
are removed from the 7.1 bed by the standard's own constant (each at -3 dB in the speaker under it)
instead of the gains the stream's metadata states, which is no longer read; on twelve DTS:X tracks
every frame stated exactly that fold, so the output is bit-identical. The alternate presentations,
which carry objects (some IMAX-labelled streams), are refused and play as 7.1, and DTS:X is rendered
only into the two layouts that carry the bed and the four heights one to one; any other layout
keeps the channel bridge. The object position mapping and the fold renderer are gone (#744).
- **The documentation no longer says TrueHD Atmos objects are always lost.** The Known limitations
entry in `docs/formats.md`, the `AudioDelivery.bridged` reference and the channel bridge's notes
predate `objectAudioRendering` (#731) and said objects and heights never survive. They now say the
Expand All @@ -32,6 +55,21 @@ the public-API contract.
decode. `aetherctl play` takes `--atmos-bed` as `serve` does and prints them: on a TrueHD Atmos
remux, `obj=13/13 up=4 bed=7.1.4`.

### Fixed

- **The persistent HTTP/1.1 ingest holds the next segment request whenever the buffer is full.**
Backpressure applied only while a segment still had bytes left over, so a segment whose last chunk
was taken just under the 16 MB ceiling sent the next request at once, past the ceiling. The
producer now waits for room between segments either way (#732; a test pins the case, and the
timing test that caught it on CI no longer flakes).
- **A slow origin no longer fails large HEVC-in-TS segments on the persistent HTTP/1.1 ingest.**
Segment bodies stream to the demuxer instead of downloading whole first, the 45 s ceiling covers
only the wait for a response head, and a body ends on its size or on 30 s without a byte (typed
`.stalled` rather than the socket's Code=60). One request per origin at a time, so a seek never
opens a second connection beside a response in flight (#732).
- **The relay admits only http and https origins.** A playlist naming another scheme (`file://`
included) answers 404 instead of reaching URLSession.

## [7.39.0] - 2026-10-17

**Not built by default any more: TrueHD Atmos and DTS:X object rendering, Profile 7 FEL dual-layer
Expand Down
2 changes: 1 addition & 1 deletion Examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Three samples covering different audiences:
```
https://github.com/superuser404notfound/AetherEngine
```
Dependency Rule: Up to Next Major Version, starting from `7.39.0`. Add the `AetherEngine` library product to your app target.
Dependency Rule: Up to Next Major Version, starting from `7.40.0`. Add the `AetherEngine` library product to your app target.

3. **Drop the file in.** Replace the Xcode template's default `App.swift` (or whatever the generated `@main` file is called) with the contents of [`MinimalPlayerApp.swift`](MinimalPlayer/MinimalPlayerApp.swift). The file is self-contained: it defines both the `@main App` struct and the `ContentView`.

Expand Down
10 changes: 5 additions & 5 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 11 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,14 @@ let package = Package(
name: "TrueHDObjectAudio",
description: "Link LibTrueHD and the object renderer, so objectAudioRendering can render TrueHD Atmos objects to APAC."
),
.trait(
name: "TrueHDReferenceRender",
description: "Measurement only: with TrueHDObjectAudio and a LibTrueHD built with matrix interpolation, AETHER_TRUEHD_REFERENCE_RENDER=1 decodes TrueHD Atmos exactly and mixes its objects, the reference the bed split is measured against. Never ship it.",
enabledTraits: ["TrueHDObjectAudio"]
),
.trait(
name: "DTSXObjectAudio",
description: "Link LibDTSX and the object renderer, so objectAudioRendering can render DTS:X heights and objects to APAC."
description: "Link LibDTSX and the object renderer, so objectAudioRendering can render DTS:X heights to APAC."
),
.trait(
name: "DualLayerDolbyVision",
Expand Down Expand Up @@ -81,13 +86,13 @@ let package = Package(
// TrueHD decoder that also decodes the Atmos object presentation (beds, objects, OAMD) for
// `objectAudioRendering` (#731). The Rust `truehd` crate (Apache-2.0) behind a C API, prebuilt
// as an xcframework like LibDovi so consumers need no Rust. Pinned to the minor for the same reason.
.package(url: "https://github.com/superuser404notfound/LibTrueHD", .upToNextMinor(from: "2.3.1")), // 2.3.1: truehd_atmos_version_string() names the truehd commit actually built in (read from Cargo.lock, LibTrueHD#2), same decoder; 2.3.0: decodes about a third faster again (harletty's truehd speed work: blocks decoded in place, table-driven Huffman, vectorised prediction filters, whole-block matrices; truehdd#42 included), byte-identical output; 2.2.1: rust_eh_personality exported weak, so it coalesces with LibDovi's copy and a host with its own Objective-C and C++ stays within compact unwind's three personality routines (#752); same decoder; 2.2.0: decodes about 30% faster (the truehd crate with its block and PCM buffers sized to the samples they hold, truehdd#42, from a fork of upstream main until that is merged), byte-identical output; 2.1.0: x86_64 in the iOS simulator slice, so a generic iOS Simulator build of a host links; same decoder; 2.0.0: transferred from Silo-Server/SiloObjectAudio and renamed (product TrueHD), rebuilt from source; same decoder as SiloObjectAudio 1.0.0
// DTS:X decoder for `objectAudioRendering`: lossless DTS-HD MA with the encoder's height and object
// folds removed from the bed, the fixed heights, and the alternate presentations' objects placed in
// the same room coordinates as LibTrueHD's. The Rust `dca` crate behind a C API, a port of FFmpeg's
.package(url: "https://github.com/superuser404notfound/LibTrueHD", .upToNextMinor(from: "3.0.0")), // 3.0.0: API 2, the bed companion (the channel presentation decoded with the object presentation) and a per-handle interpolation switch; the interpolating decode is an opt-in build feature, off in the shipped binary (#731); 2.3.1: truehd_atmos_version_string() names the truehd commit actually built in (read from Cargo.lock, LibTrueHD#2), same decoder; 2.3.0: decodes about a third faster again (harletty's truehd speed work: blocks decoded in place, table-driven Huffman, vectorised prediction filters, whole-block matrices; truehdd#42 included), byte-identical output; 2.2.1: rust_eh_personality exported weak, so it coalesces with LibDovi's copy and a host with its own Objective-C and C++ stays within compact unwind's three personality routines (#752); same decoder; 2.2.0: decodes about 30% faster (the truehd crate with its block and PCM buffers sized to the samples they hold, truehdd#42, from a fork of upstream main until that is merged), byte-identical output; 2.1.0: x86_64 in the iOS simulator slice, so a generic iOS Simulator build of a host links; same decoder; 2.0.0: transferred from Silo-Server/SiloObjectAudio and renamed (product TrueHD), rebuilt from source; same decoder as SiloObjectAudio 1.0.0
// DTS:X decoder for `objectAudioRendering`: lossless DTS-HD MA in its standard presentation, the
// four fixed heights removed from the 7.1 bed by the standard's constant fold and returned on their
// own channels; presentations with objects are refused. The Rust `dca` crate behind a C API, a port of FFmpeg's
// LGPL-2.1-or-later dca decoder, so the built library is LGPL-3.0-or-later and ships as a dynamic
// framework the app embeds, like FFmpegBuild's (#798). Pinned to the minor for the same reason.
.package(url: "https://github.com/superuser404notfound/LibDTSX", .upToNextMinor(from: "2.0.0")), // 2.0.0: DTSX.framework, dynamic, exporting only the C API, with dSYMs; NOTICE states the LGPL; same decoder, bit-identical output (#798); 1.0.1: rust_eh_personality weak (#752); dca 0.8.0 (harletty-bridge a717bae); the first two frames after a start or seek only prime the decoder
.package(url: "https://github.com/superuser404notfound/LibDTSX", .upToNextMinor(from: "3.0.0")), // 3.0.0: the standard presentation only, its heights removed by the standard's constant fold without reading the metadata, presentations with objects refused; same output for standard DTS:X (#744); 2.0.0: DTSX.framework, dynamic, exporting only the C API, with dSYMs; NOTICE states the LGPL; same decoder, bit-identical output (#798); 1.0.1: rust_eh_personality weak (#752); dca 0.8.0 (harletty-bridge a717bae); the first two frames after a start or seek only prime the decoder
],
targets: [
.target(
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ A scannable summary; the depth for each row lives in **[docs/formats.md](docs/fo
| Video (SW) | AV1 (dav1d) without HW, and AV1 High / Professional (4:4:4, 4:2:2, 12-bit) everywhere, VP9 / VP8, MPEG-4 Part 2 / MPEG-2 / VC-1, QuickTime RLE, the Flash tail (Sorenson Spark, On2 VP6) and anything else the FFmpeg build carries a decoder for (software is the default route; only HEVC, H.264 and HW-decodable AV1 go native), H.264 High 4:2:2 / 4:4:4 / 10 and HEVC Rext where VideoToolbox has no HW decoder (Intel Macs, older chips), interlaced H.264 (AVPlayer does not deinterlace; on VOD the declared field order is verified against decoded frames, so progressive-in-interlaced-carriage keeps hardware decode); GPU deinterlace (yadif_videotoolbox, Metal, field-rate by default) with a CPU bwdif fallback |
| HDR | HDR10, HDR10+ (per-frame ST 2094-40), Dolby Vision (P5, P7 on the native route as single-layer 8.1 (HDR10 base layer on a display without Dolby Vision), or composed dual-layer on Metal with per-frame DV metadata for FEL when `preferDualLayerDolbyVision` is set, the `DualLayerDolbyVision` trait is on and VideoToolbox decodes HEVC in hardware, P8.1, P8.4, AV1 P10.x), HLG, HDR Vivid (played on its HLG / PQ base, CUVA metadata reported by `probe`) |
| Audio | AAC, AC3, EAC3, FLAC, ALAC stream-copy lossless; TrueHD / MLP / DTS / DTS-HD MA / MP3 / MP2 / Opus / Vorbis / LPCM (incl. Blu-ray, G.711) / WMA (Standard, Pro, Lossless, Voice) / Nellymoser / ADPCM-SWF / Speex bridge to EAC3 5.1 (default) or lossless FLAC |
| Dolby Atmos | EAC3+JOC stream-copied on every route (HDMI MAT 2.0, AirPods spatial, BT downmix) except the opt-in Profile 7 FEL dual-layer route (`preferDualLayerDolbyVision`, `DualLayerDolbyVision` trait), which runs on the software host and plays an E-AC-3 JOC track as its 5.1 / 7.1 bed without the objects. No container reliably declares JOC pre-decode, so an honest `TrackInfo.isAtmos` needs a bounded decode: `AetherEngine.probeDetectingAtmos(url:/source:)` answers for a details screen without starting playback, and `LoadOptions.confirmAtmos` has the running session confirm its own tracks in the background and republish `audioTracks`. Both are opt-in and neither sits on the playback-start path. TrueHD Atmos keeps its heights too, opt-in through `LoadOptions.objectAudioRendering` in a build with the `TrueHDObjectAudio` trait ([Optional components](#optional-components)): the engine decodes the Atmos objects, renders them into a speaker bed and delivers Apple Positional Audio, which tvOS sends to an Atmos receiver as Dolby MAT and iOS plays as Spatial Audio ([details](docs/formats.md#truehd-atmos-with-heights)). DTS:X takes the same route with the `DTSXObjectAudio` trait, its heights and objects unfolded from the compatible 7.1 ([details](docs/formats.md#dtsx-with-heights)) |
| Dolby Atmos | EAC3+JOC stream-copied on every route (HDMI MAT 2.0, AirPods spatial, BT downmix) except the opt-in Profile 7 FEL dual-layer route (`preferDualLayerDolbyVision`, `DualLayerDolbyVision` trait), which runs on the software host and plays an E-AC-3 JOC track as its 5.1 / 7.1 bed without the objects. No container reliably declares JOC pre-decode, so an honest `TrackInfo.isAtmos` needs a bounded decode: `AetherEngine.probeDetectingAtmos(url:/source:)` answers for a details screen without starting playback, and `LoadOptions.confirmAtmos` has the running session confirm its own tracks in the background and republish `audioTracks`. Both are opt-in and neither sits on the playback-start path. TrueHD Atmos keeps its heights too, opt-in through `LoadOptions.objectAudioRendering` in a build with the `TrueHDObjectAudio` trait ([Optional components](#optional-components)): the engine decodes the Atmos objects, renders them into a speaker bed and delivers Apple Positional Audio, which tvOS sends to an Atmos receiver as Dolby MAT and iOS plays as Spatial Audio ([details](docs/formats.md#truehd-atmos-with-heights)). DTS:X takes the same route with the `DTSXObjectAudio` trait, its four heights unfolded from the compatible 7.1 into 7.1.4 or 9.1.6 ([details](docs/formats.md#dtsx-with-heights)) |
| Surround | 5.1 / 7.1 with correct `AudioChannelLayout` |
| Audio-only | `LoadOptions.audioOnly`: lean pipeline, no video machinery, system Now-Playing on tvOS / iOS |
| Background audio | Audio keeps playing when the app backgrounds on iOS: native AVPlayer stays alive, the software path drops video and keeps decoding audio (`backgroundPlaybackEnabled` / `pictureInPictureActive`); a paused session survives quick app switches for a grace window (`backgroundTeardownGraceSeconds`, default 15 s) before the wedge-safe teardown runs. tvOS tears down immediately (wedge-safe) unless a PiP window is active, which keeps the pipeline and loopback server alive; with PiP the software path keeps decoding video too (the window needs frames). Hosts gate corrective actions on the published `isSessionReady` |
Expand Down Expand Up @@ -387,7 +387,7 @@ Subtitle cues land in raw source PTS; render the overlay against `player.sourceT
Install via Swift Package Manager:

```swift
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "7.39.0")
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "7.40.0")
```

Three samples ship in `Examples/`:
Expand Down Expand Up @@ -634,10 +634,10 @@ Browse all of this as a searchable site at **[aetherengine.superuser404.de](http
AetherEngine uses [Semantic Versioning](https://semver.org). The public API surface, every `public` declaration in `Sources/AetherEngine/`, is the stability contract. **Major** removes / renames public symbols or breaks adopters; **Minor** adds public API or codec / format support; **Patch** fixes bugs with no public API change. `internal` types are not part of the contract.

```swift
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "7.39.0")
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "7.40.0")
```

Pin to `.upToNextMinor(from: "7.39.0")` for stricter teams that prefer to opt into minor bumps explicitly.
Pin to `.upToNextMinor(from: "7.40.0")` for stricter teams that prefer to opt into minor bumps explicitly.

## Requirements

Expand Down Expand Up @@ -672,11 +672,11 @@ These components may be subject to third-party patent licensing. Integrators who
To enable them, list the traits on the package dependency:

```swift
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "7.39.0",
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "7.40.0",
traits: ["TrueHDObjectAudio", "DTSXObjectAudio", "DualLayerDolbyVision", "DolbyVisionStillReshaping"])
```

In an Xcode project, a remote package's traits are set in the package dependency editor (Project › Package Dependencies), and Xcode stores them in the `.pbxproj`. XcodeGen cannot express them yet (2.45.4 drops a `traits:` key on a package without a word), so an XcodeGen project either patches the generated `.pbxproj` after each generate, or depends on a small local package that depends on AetherEngine with the traits set and re-exports it (`@_exported import AetherEngine`). [Probes/AllTraits](Probes/AllTraits/Package.swift) is that package with every trait on, and `Probes/DolbyVisionPulse/Device/PulseEngine` one with `DualLayerDolbyVision` alone. The engine itself builds with them through `swift build --traits TrueHDObjectAudio,DTSXObjectAudio,DualLayerDolbyVision,DolbyVisionStillReshaping` (or `--enable-all-traits`); CI builds and tests both the default and the all-traits configuration, and builds the all-traits engine for tvOS and iOS through `Probes/AllTraits`.
In an Xcode project, a remote package's traits are set in the package dependency editor (Project › Package Dependencies), and Xcode stores them in the `.pbxproj`. XcodeGen cannot express them yet (2.45.4 drops a `traits:` key on a package without a word), so an XcodeGen project either patches the generated `.pbxproj` after each generate (a `traits = ( TrueHDObjectAudio, DTSXObjectAudio, );` list in the package's `XCRemoteSwiftPackageReference`, which Xcode 27 builds with), or depends on a small local package that depends on AetherEngine with the traits set and re-exports it (`@_exported import AetherEngine`). [Probes/AllTraits](Probes/AllTraits/Package.swift) is that package with every trait on, and `Probes/DolbyVisionPulse/Device/PulseEngine` one with `DualLayerDolbyVision` alone. The engine itself builds with them through `swift build --traits TrueHDObjectAudio,DTSXObjectAudio,DualLayerDolbyVision,DolbyVisionStillReshaping` (or `--enable-all-traits`); CI builds and tests both the default and the all-traits configuration, and builds the all-traits engine for tvOS and iOS through `Probes/AllTraits`.

## Support

Expand Down
2 changes: 1 addition & 1 deletion Sources/AetherEngine/AetherEngine+Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ extension AetherEngine {
///
/// Between releases, and in a consumer that pins an unreleased commit for a device test, this
/// names the last PUBLISHED version the checkout descends from, not every patch in it.
public nonisolated static let version = "7.39.0"
public nonisolated static let version = "7.40.0"
}
Loading
Loading