Skip to content

chore: merge upstream AetherEngine 7.40.1 into the Silo fork - #23

Merged
Quick104 merged 223 commits into
mainfrom
sync/upstream-7.40.1
Oct 19, 2026
Merged

Quick104 merged 223 commits into
mainfrom
sync/upstream-7.40.1

Conversation

@Quick104

@Quick104 Quick104 commented Oct 19, 2026 •

Copy link
Copy Markdown

Summary

Brings the Silo fork to upstream AetherEngine 7.40.1, keeping every Silo patch. It builds on #22 (the 7.38.1 merge, which had not been built or tested): this branch contains #22's merge commit, so merging this PR with a merge commit also lands #22.

Upstream 7.39.0 moved TrueHD Atmos and DTS:X object rendering (the APAC path, with LibTrueHD and LibDTSX), Profile 7 FEL composition and Dolby Vision Profile 5 / 10.0 still reshaping behind package traits that are off by default, because those components may need third-party patent licenses. The fork keeps upstream's defaults, so a host that sets no traits builds without them.

What changed

Upstream changes that matter to Silo:

Merge conflicts, resolved by keeping both sides:

  • ci.yml: upstream's 50-minute timeout and the fork's separate authorization-suite run.
  • HLSOriginRelay: both sides added the http/https origin check; upstream's form is kept.
  • PlayerState .bridged and its docs/api.md row: upstream's trait wording with the fork's correction that only FLAC keeps the bed lossless.
  • docs/api.md objectAudioRendering row: upstream's text plus the ObjectAudioRendering reference PublicAPIDocumentationTests requires.
  • docs/cli.md: upstream's text with the fork's apac.31.02 level for 8 channels. docs/formats.md: upstream's text with an unbalanced parenthesis fixed.
  • DocumentedConstantsTests: upstream's trait gate, plus the fork's 8- and 10-channel APAC CODECS checks.

Test plan

  • Device / OS: macOS 27, Xcode 27, Swift 6.4 for the package. iOS 27.0 simulator for a silo-apple build pinned to this revision.
  • Source media: a TrueHD Atmos 7.1.4 1080p H.264 MKV, and a 1080p H.264 MKV with AC-3 at about 35 Mbit/s on a server with 5-minute access tokens.
  • Result:
    • swift build passes with the default traits and with all four shipping traits.
    • Default traits: 785 XCTest (1 skipped), 17 SMB and 4,681 Swift Testing tests pass; the authorization group passes 56 of 56.
    • All four traits, object-audio filter: 29 XCTest and 148 Swift Testing tests pass.
    • silo-apple builds for iOS, tvOS and macOS on this revision. The TrueHD Atmos title plays (TrueHD decoded at 48 kHz, 8 channels). The direct-play session read across three token rotations with no refused request.

Evidence: https://evidence.siloserver.org/r/aetherengine/sync-upstream-7.40.1/

Checklist

  • CHANGELOG.md updated (upstream's entries; the fork's stay under Unreleased)
  • Commit messages follow Conventional Commits
  • The fix lives in the engine, not in a host-side workaround
  • Public API changes are intentional and documented (all from upstream)

AI disclosure

  • Harness: Claude Code, running in T3 Code
  • Model: claude-opus-5-5
  • Involvement: AI-generated merge and conflict resolution, built and tested as above. Not yet reviewed by a person.

🤖 Generated with Claude Code

Note

Merge upstream AetherEngine 7.40.1 into the Silo fork

Brings the upstream 7.40.1 changes into the fork in one merge. The largest areas of change:

  • Adds a new Dolby Vision Profile 7 FEL software path: DualLayerVideoDecoder, DoviFelComposer (Metal composition), DoviRpuConverter.separateDualLayers, DoviRpuGuard input validation, and picture alignment/restamping — all gated behind the new opt-in DualLayerDolbyVision package trait, with a new preferDualLayerDolbyVision load option that defaults to false.
  • Reworks PersistentHTTP1Transport.swift to stream response bodies instead of downloading whole responses, and makes HLSVODIngestReader.swift feed segments incrementally with a primed first-response body.
  • Adds live request-header updates via the new public updateRequestHeaders API and RequestHeaderStore, including HTTP 401/403 credential-refusal detection, retry accounting, and up to three bounded revives after renewal.
  • Restructures the package into four opt-in traits (TrueHD object audio, DTS:X object audio, dual-layer Dolby Vision, still reshaping) in Package.swift; all are disabled by default and the LibTrueHD/LibDTSX dependencies move to 3.0.
  • Adds spatial-audio work (TrueHD bed splitting via BedSplitter, DTS:X standard-presentation-only decoding, TrueHDMajorSync.isAtmos detection), H.264 Matroska CFR timestamp reconstruction, in-band HEVC parameter-set completion, DVD VIDEO_TS folder playback, FLAC block-size repair, resident-range reporting for software VOD, and the Dolby Vision pulse probe tooling.
  • Risk: this is a large behavioral merge — key items to inspect are the new preferDualLayerDolbyVision routing gates in VideoRoutingPolicy.swift and SoftwarePlaybackHost.swift, the removed 45-second resource timeout in the persistent transport, credential-refusal reclassification of software failures, and the trait defaults turning all optional components off for existing consumers.

Macroscope summarized 898946d.

yipengfei329 and others added 30 commits October 13, 2026 11:04
A DiscDirectoryReader whose listing holds VIDEO_TS/ files is now read as
DVD-Video instead of Blu-ray. The title-set code of wrapDVD moves into
dvdTitleSet, which takes a listing and a way to read one whole file, so an
image and a folder share the VMGI filtering, durations, chapters, languages and
the selected title's VTS IFO. wrapDVDFolder concatenates the selected title
set's VOBs in part order with MultiFileConcatIOReader; that is the same run of
bytes as their sectors on a disc, so the cell spans and the VTS time map apply
unchanged. A listing without VIDEO_TS/ is read as Blu-ray, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: OpenAI Codex <noreply@openai.com>
Co-Authored-By: OpenAI Codex <noreply@openai.com>
… routed)

Adds the pieces that rebuild a Profile 7 FEL picture, with nothing selecting
them yet.

- DoviFelComposer (Metal): rebuilds the 12-bit picture from base layer,
  enhancement layer and RPU: reshaping curves (polynomial and MMR), inverse
  NLQ, MMR chroma, bilinear EL upsampling, ordered dither down to 10-bit,
  output in a pooled P010 buffer, composeAsync bounded at maxFramesInFlight.
  DoviMetalParams is the parameter ABI, DoviShaderSource the kernels. The
  output pool survives a seek flush (flush() trims idle buffers,
  releaseOutputPool() drops it on teardown): dropping it made post-seek
  frames allocate a second full pool beside the buffers the renderer still
  held, about +260 MB at 4K for ~0.6 s per seek; measured end to end with
  the decoder in the next PRs, peak 674 -> 500 MB over 12 seeks.
- DoviDisplayMetadata: attaches the Profile 8.1 RPU as DolbyVisionRPUData
  plus DolbyCompatibilityID (a CFString: a CFNumber aborts the render
  server). Both keys are undocumented; they are defined in this one file
  and UndocumentedDolbyVisionKeysTests fails if either literal appears in
  another source file.
- DoviRpuConverter.separateDualLayers splits one packet into base layer,
  enhancement-layer packet, converted RPU and composer parameters;
  convertPacketToProfile81 is now that walk with the EL dropped. The RPU
  attached to composed frames is converted with mode 2 (the composer has
  already applied the reshaping and added the residual, so the display side
  must not reshape again); DoviComposedFrameRpuTests pins this.

Nothing calls the composer, so no behaviour changes for any source. Public
API: none. Not in this PR: the dual-layer decoder, the routing flag and
host wiring, the aetherctl tooling, and dolby-vision-fel.md (comments here
cite its section numbers; the document arrives with the route PR).

Tests: GPU curve sweep, edge cases and adversarial inputs, layout probe (the
GPU reads each parameter sub-block back at the offset Swift wrote it),
shader/ABI sync, dither, enhancement upsampler, FEL math, FEL and
CRC-valid-mutation fuzzers, converter and guard tests, async compose,
look-ahead pool sizing. The real-clip tests skip without DV_FEL_TESTS_DIR.
…e dual-layer decoder

Four self-contained pieces the dual-layer Profile 7 decoder is built from,
none of them wired to anything yet:

- HEVCPictureOrderCount: the H.265 picture order count (§8.3.1) of one
  layer, read from the parameter sets and each picture's first slice
  segment header. No decoding. Anything it cannot follow (unknown PPS/SPS,
  truncated header, pictures before the first IRAP) answers nil rather than
  a guess.
- DualLayerPictureAlignment: derives each layer's display slot from its
  POC and gives each enhancement-layer packet the presentation time of the
  base-layer picture in the same slot, so a stream whose enhancement layer
  has B-frames over a base layer without them pairs the right pictures.
  Falls back to access-unit pairing when a layer's POC is unavailable or
  the slots disagree by more than 16.
- FrameSideDataRing: fixed-capacity (32) ring of per-frame side data keyed
  by PTS, looked up within +-300 ticks.
- OrderedDelivery: hands composed frames across the VideoToolbox / Metal
  queue boundary in decode order.

Also exposes A53SEIParser.unescapeRBSP and parseHVCCParameterSets
(DoviRpuConverter+Probe) at module scope, which the POC reader and its
test use. No behaviour change: nothing calls these types. Not in this PR:
the decoder that uses them, the routing, the docs.

Tests: DualLayerPictureAlignmentTests, FrameSideDataRingTests,
OrderedDeliveryTests.
libdovi is built panic=abort, so a single panic ends the host process. A
CRC-valid but hostile Dolby Vision RPU could reach one on the native
Profile 7 -> 8.1 conversion.

Adds DoviRpuGuard: it checks the RPU CRC and pre-scans the payload for the
inputs that make libdovi 3.4 panic or allocate without bound (linear
interpolation pieces, polynomial/MMR mixes that break the writer, pivot and
block counts the payload cannot hold, undefined display-management block
lengths). The existing paths now check each RPU with it before parsing: the Profile 8.1
conversion in DoviRpuConverter (convertPacketToProfile81), the
enhancement-layer-type probe, and the DolbyVisionRecordAudit RPU read. The
mode 2 convert -> write pair on a parsed RPU is factored into
DoviRpuConverter.convertedUnspec62 so the tests drive production's own entry
point. A rejected RPU degrades the frame to the HDR10 base exactly as a
libdovi failure already did (superuser404notfound#135).

This hardens the existing native 8.1 conversion; it changes no routing and
adds no Dolby Vision FEL code. The guard knows the paths listed above; it
does not prove there are no others.

Tests: DoviRpuGuardTests (crafted RPUs, verdicts, conversion round trip) and
a DolbyVisionRecordAudit case. The CRC-valid mutation fuzz and the
real-file guard test arrive with the composer, whose parameter walk they
also exercise.
…or reordered streams

Adds the SampleBufferRenderer pieces the dual-layer Dolby Vision path needs
for pacing, as inert, opt-in additions:

- felVideoLookaheadFrames (9) and mayEnqueueVideoFrame / setFELLookahead:
  a gate that runs BEFORE decode and refuses a compressed frame more than
  nine frame intervals ahead of the playback clock, so composed 4K frames
  (about 25 MB each) cannot pile up behind Apple's readiness gate alone.
- displayOrderPTS: estimates a packet's display-order PTS in decode order
  from the largest amount a later packet fell below the highest PTS seen,
  so B-frame streams do not swing the cap with the position in the GOP. A
  gap larger than HEVC's 16-picture reorder ceiling is treated as a
  timestamp discontinuity and does not poison the learned lead.

Nothing calls the gate yet and it is off unless setFELLookahead(enabled:)
is called, so non-FEL pacing is unchanged. The decoder and the host wiring
come in later PRs; the composer-side pool sizing that follows this cap
(DoviFelComposer.outputPoolAllocationThreshold) is pinned in the composer PR.

Tests: SampleBufferRendererLookaheadTests, SampleBufferRendererReorderDepthTests.
…tion

Matroska TrueHD reaches the software read-ahead with no packet duration and millisecond timestamps
that repeat (0.83 ms frames on a 1/1000 time base). SoftwarePacketCoverage.insert rejects a zero
duration, so audio coverage stayed empty, the combined A/V frontier was nil, and no seek ever hit
the packet cache: every seek, even back to a spot just played, refetched from the source.

Audio arrives in presentation order, so a duration-less packet now proves coverage up to the next
packet's timestamp, as long as the gap is at most 0.5 s (a larger one is a hole). Packets with a
duration are unchanged.

Apple TV 4K, 4K Matroska with TrueHD 7.1 over HTTP, 5 seeks per run: cache hits went from 0/10 to
5/5 in every run; AC-3 and video-only already hit 10/10. The new test fails without the change.
The software path's compressed packet cache was a session file on disk. On an Apple TV 4K (A12)
appends to it took 100 to 600 ms while a download ran; the producer holds the store across an
append, the demux thread's reads waited behind it (and its own reads stalled up to a second), and
video it had already parked reached the decoder late. The layer dropped it.

On tvOS the store now lives in memory (SoftwarePacketMemoryFIFO behind a SoftwarePacketStore
protocol the disk FIFO also conforms to). Records keep the disk layout in chunks of purgeable VM:
unread data stays resident, a chunk the reader has finished is made volatile, so the kernel may
reclaim it and it stops counting against the footprint; a seek into reclaimed history is a cache
miss and refetches. Bounds: 96 MB forward, 128 MB of played history. AVPlayer keeps its
progressive-download cache in memory on tvOS as well. macOS, iOS and visionOS keep the disk store.

Apple TV 4K (A12), 4K Dolby Vision title over HTTP, 150 s soaks with 5 seeks: disk store 42/39/22
drops, memory store 0 to 3 in 8 of 9 runs (one run 26, not reproduced), no slow reads left. Peak
footprint ~655 MB against ~580 MB with the disk store.
DualLayerVideoDecoder: the decoder half of the dual-layer route. It splits
each packet into base layer, enhancement layer and RPU
(DoviRpuConverter.separateDualLayers), decodes both layers on VideoToolbox
through two HardwareVideoDecoder sessions, pairs them by HEVC picture
order, composes with DoviFelComposer and delivers the frames in decode
order with the Profile 8.1 RPU attached per frame.

- Pairing: DualLayerPictureAlignment restamps each enhancement-layer
  packet with the time of the base picture in the same display slot, so an
  enhancement layer coded with B-frames over a base layer without them is
  no longer paired with a neighbouring frame's residual. A layer keeps
  access-unit pairing until its next sequence-opening IRAP after a seek.
- Bounded memory: base-layer skew K = 3 frames, at most 10 waiting
  enhancement frames, output pool 15 plus a reserve of 3. A late or broken
  EL falls back to the base layer for that frame.
- Seeks: a seek flushes every stage. A stale EL is never paired with a
  post-seek base layer and a stale BL is never delivered. The composer's
  output pool is kept across the flush instead of being dropped (it made
  the frames after a seek allocate a second pool beside the buffers the
  renderer still held): peak footprint on a 12-seek run of one test clip
  went from 674 MB to 500 MB. The pool is dropped on close.
- HardwareVideoDecoder gains an opt-in no-copy block source and a
  parameter-set open for the enhancement layer; both are off for every
  existing caller. FelResidualMeasure (the AETHER_FEL_MEASURE diagnostic,
  off by default) is here because the decoder calls it.

Nothing constructs the decoder yet: no host code selects it until the route
PR, so no source plays differently after this one. Public API: none. Not in
this PR: the capability gate, the preferDualLayerDolbyVision flag, host
wiring, dolby-vision-fel.md.

Tests: decoder, GPU slot wait, seek realignment (flush of both sessions and
the composer, stale EL not composed, waiting BL dropped at the seek, seek
during an in-flight composition), stream classification and packet balance,
CPU model, output-pool-across-flush, real-clip tests (gated on
DV_FEL_TESTS_DIR, skipped without), and a subtitle compositor test that the
Dolby Vision attachments survive compositing.
…erDualLayerDolbyVision

Host wiring for the composer and decoder. LoadOptions.preferDualLayerDolbyVision
(default false) makes a Profile 7 FEL file play through SoftwarePlaybackHost
and DualLayerVideoDecoder when the box can afford it; otherwise the existing
Profile 8.1 conversion is untouched. With the flag off no source plays
differently.

- DualLayerRouteDecision: a pure capability gate. The route is taken only
  for FEL, with a proven hardware HEVC session, a seekable VOD,
  dolbyVisionHandling .automatic and a stream inside the real-time budget
  ((3840x2160 + 1920x1080) x 30 = 311.04 Mpx/s; a 24p UHD FEL with a
  quarter-area EL needs 80%; an unknown frame rate is budgeted as 24). Every
  Profile 7 load logs exactly one `DV P7 dual-layer route: taken|not taken
  (<reason>)` line. It fails closed: when the base-layer size is unknown the
  budget cannot be evaluated, so the route is refused rather than assumed.
  The budget is an assumption until the Apple TV run confirms it.
- Wiring in AetherEngine, SoftwarePlaybackHost, VideoRoutingPolicy,
  CodecRoutePolicy and the reload path (preferDualLayerDolbyVision is a
  tuning field, correctable through reloadAtCurrentPosition(applying:)).
  An `hev1` source with in-band parameter sets (an empty hvcC) is completed
  from the head of the stream so it can be classified and take the route,
  the same rebuild the native path already does for its init.mp4.
- Audio trade-off, documented in LoadOptions, api.md and
  dolby-vision-fel.md and pinned by DualLayerRouteAudioTests: the route runs
  on the software host, whose audio FFmpeg decodes and the sample-buffer
  audio renderer plays, and on tvOS only AVPlayer emits Atmos objects. An
  E-AC-3 (JOC) track therefore plays as its 5.1 / 7.1 channel-bed core,
  without the objects, where the native route would hand it to AVPlayer
  unchanged. Not engineered around.
- Docs: dolby-vision-fel.md (the design), api.md, formats.md, README, and
  the DocumentedConstantsTests pins that tie the document's numbers to the
  code (route budget, pipeline bounds, picture-order pairing bound). The
  document arrives here, whole; code comments in the earlier PRs cite its
  section numbers.

Mac soak, measured BEFORE the output-pool fix of the decoder PR (peaks are
pre-fix; with it 12 seeks peak at 500 MB instead of 674 MB), M2 Pro, debug
aetherctl, --window:

| Run | Route | Composed / failures | Seeks landed | Drops | Steady | Peak |
|---|---|---|---|---|---|---|
| 217 s, 12 seeks | taken, budget 80% | 6143 / 0 | 15 | 6 / 6206 | 328-331 MB | 674 MB |
| 120 s, 11 seeks | taken | 4248 / 0 | 12 | 7 / 4275 | 282 MB | 600 MB |
| pause + seek while paused | taken | 2005 / 0 | 1 | 1 / 1949 | 330 MB | 352 MB |

Apple TV results: pending, nothing measured on device yet.

Tests: DualLayerRouteDecisionTests (one per exit), VideoRoutingPolicyTests,
SoftwareDecoderSelectionTests, InBandParameterSetRebuildTests,
DualLayerEdgeCaseTests, DualLayerRouteAudioTests, LoadOptionsTests,
DocumentedConstantsTests. Not in this PR: the pulse probe and the aetherctl
tooling.
…ming harness, analysis)

Probes/DolbyVisionPulse: a reproducible measurement of whether a panel
follows Dolby Vision metadata. Clips keep the pixels constant and switch
only the RPU (or, for the enhancement-layer clip, only the residual) every
72 frames, so a phone filming the television shows whether the display
uses the metadata and the composed pixels. It is a measurement, not a unit
test, and it commits no media.

- Scripts: gen_pulse.py (pattern, RPU configs, EL residual design),
  build.sh, serve.py, run_tv.sh, cut.py (one phone recording per arm),
  analyze.py (verdict per arm), report.py (the change-per-patch table and
  phase-A levels), check_el.py (checks on the Mac that the EL clip carries
  the designed residual in phase B and none in A), dump_attachments.swift
  (what VideoToolbox attaches to a decoded frame), sim_recording.py (a fake
  recording that validates the analysis).
- Device/: a minimal tvOS host that plays the clips through AVPlayer,
  VideoToolbox and the FEL route (preferDualLayerDolbyVision). It uses only
  public engine API.
- CI: compiles the scripts and builds the host for the tvOS simulator, so a
  moved public API is found in CI instead of at the television.
- The pulse test is the measurement behind the consumption evidence in
  docs/dolby-vision-fel.md section 4.5, which arrived with the route PR.

No library code changes and no behaviour change. Public API: none. Not in
this PR: the aetherctl tooling. The host builds in CI; the rest needs a
television and a phone.

Checked here: Python and shell syntax of the scripts, swiftc -typecheck of
dump_attachments.swift, xcodebuild of the host for the tvOS simulator
(BUILD SUCCEEDED), swift build --build-tests of the package.
The command-line and probe side of the dual-layer Dolby Vision work. Nothing
here is host API: the probes are @_spi(Diagnostics), outside the SemVer
contract, and the host surface of the FEL route stays
LoadOptions.preferDualLayerDolbyVision alone.

- aetherctl play: --cycle-el flips the residual on a timer over a visible
  surface, --no-fel plays the same file on the native route, --surface
  presents it, and --dv-pin (DEBUG builds only) arms the consumption pin test.
- aetherctl dvsurvey: reports a title's L1 / L2 display-metadata
  distribution straight from its RPUs (DoviRpuSurvey: no decode, no GPU).
- aetherctl feltest: decodes real Profile 7 files through the FEL path and
  checks the result (AetherEngine+FelProbe: felDecodeProbe, felResidualEnabled).
- DoviDisplayMetadataDebug and AetherEngine+DolbyVisionTest: a DEBUG-only
  hook that substitutes a captured RPU on a duty cycle, the on-device
  consumption test. It compiles out of Release; DoviDisplayMetadata.attach
  gains one #if DEBUG call and is otherwise unchanged.
- docs/cli.md, the docs/api.md "public but not host API" bullet, and the
  docs/architecture.md entries for these files.

No behaviour change for a host: the SPI is unreachable without
@_spi(Diagnostics) and the DEBUG hook is off unless armed. Not in this PR:
anything on the route or the decoder.

Tests: DoviRpuSurveyResultTests (the survey's pure summaries), DoviPinTestTests
(the pin-spec grammar), and the DEBUG pin hook's disarm test in
DualLayerVideoDecoderTests.
…readable source

When the live HLS ingest's playlist was refused at the join, the probe saw no bytes and the load
published customSourceProbeFailed, which a host reads as a stream it cannot play; one that falls back
to another route on it moved the channel there for the rest of its session, over a 509 an IPTV panel
gives while another device holds the account's last slot.

The load now publishes the origin's answer, as an HTTP reader's failed open does: sourceRateLimited
(429/503/509) or sourceRefused with the status in underlyingCode, or liveSourceUnavailable when the
playlist could not be reached. docs/api.md says so beside customSourceProbeFailed.

Tests: PlaybackErrorInfoTests (refused join for 509 and 403, unreachable join).
…nding the session

An IPTV panel that limits an account's connections answers a live playlist refresh with 509 while
another device holds the last slot, and goes on refusing a session it evicted while a fresh session
from the loaded URL is admitted at once. The ingest retried 1, 2 and 4 s apart and went terminal at
12 s (on the software path, the end of the session), and each refusal armed the origin's request
pacer, so the host's reload waited up to 10 s for its first request.

- A refused or unreachable refresh (429, 5xx, transport) is asked again every second, or after the
  origin's Retry-After up to 5 s, for up to 60 s; a refused segment likewise, after its two quick
  retries.
- A session URL the loaded URL led to through redirects is asked for afresh through the loaded URL
  once refused twice in a row, or at once when it answers as gone (a 4xx other than 429 and 408).
  The tracker follows MEDIA-SEQUENCE, so the segments continue where they left off.
- The live ingest's refusals no longer arm OriginRequestBudget's pacer or lower its request budget;
  a ceiling the host declares or another path learns still binds it.

Tests: HLSLiveIngestRefusalTests.
…by swapping its item

The wireless-AirPlay route change rebuilt the session to serve it on the device's LAN
address. A live session on the HLS ingest stands on a forward-only custom source, whose
rebuild is refused (superuser404notfound#526), and the refusal was dropped: the receiver stayed on 127.0.0.1,
which it cannot reach, and played nothing while the sender reported external playback.

Such a session now swaps the player's item onto the receiver's address, and back onto the
loopback when the receiver lets go, as a sequential origin already did. The session, its
ingest and its segment cache carry on, so the source is asked for nothing again. The
swapped item rejoins its live playlist at the player's join point, as a live reload does.
On-demand sessions cross as before.

AirPlayPlaylistDecision.routeChangeHop decides which sessions swap, with tests.
docs/formats.md (Wireless AirPlay) and docs/architecture.md say how each kind crosses.
…DVD-Video VIDEO_TS folder over HTTP

feat(disc): play a DVD-Video VIDEO_TS folder over HTTP
superuser404notfound#756 routed any listing that held a VIDEO_TS/ entry to the DVD path, and a
DVD path that found no title VOBs did not fall back to Blu-ray. An image is
recognized Blu-ray first with DVD as the fallback; a folder now follows the
same order, so a hybrid rip plays its Blu-ray title and a folder whose BDMV
tree holds no playable title still plays its DVD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…ctive-video-decoder

fix(playback): report the software host's actual video decoder
…h264-missing-head-cadence

fix(demux): repair variable-cadence H264 heads and hierarchical partial offsets

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…peruser404notfound#761)

When the whole-file probe hands its held sample to the slot session, nothing
is dequeued in between. A non-video packet arriving while the session had no
pending picture returned false and went back into the parent's own queue,
which is drained ahead of the session's ready queue, so it overtook audio the
session had already taken: on the missing-head fixture audio DTS -1024 left
after 16384. It now queues behind the session's ready packets; in steady state
the demuxer drains before every read, so nothing else changes.

The runtime harness now requires every non-video stream to leave in DTS order
(fails on the previous code), and the all-stream budget check drains between
packets as the demuxer does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…64-frame-cadence

# Conflicts:
#	CHANGELOG.md
…es (superuser404notfound#762)

The clamped-CFR verdict and its rewriter derived PTS from picture order and
DTS from decode order without checking that one does not precede the other.
A stream whose ranks lag their decode index by more than the reorder delay
would have been handed out with PTS < DTS. Both now fail closed on that, the
verdict back to the previous path and the rewriter to untouched packets.

Also covers the path superuser404notfound#762 changed for superuser404notfound#511 files: a coding-order ladder on a
whole-tick lattice still classifies as a permutation, not a reconstruction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…pcoming hint

The dual-layer decoder delivers frames in decode order. The renderer held four of them to reorder,
which left the FEL look-ahead window four frames short and dropped about one frame in five on
Apple TV. AVSampleBufferVideoRenderer sorts by presentation time itself if it is told the lowest
timestamp still to come, so the FEL path now hands every frame over as it arrives and sets
presentationTimeExpectation = .minimumUpcoming(floor).

PresentationFloor computes that floor: the first gap in the frame grid, half a frame up, given up
once the clock passes a slot that never arrives. After a seek it starts at the skip threshold,
since B-frames below the next anchor arrive after it.

Apple TV, three rounds: drops 17/296/289 -> 6/5/5, same memory footprint. Without a frame rate the
renderer keeps the reorder buffer.
Vincent Herbst and others added 10 commits October 18, 2026 12:51
…uperuser404notfound#731)

The exact decode and the size, snap, zone, screen and elevation mapping
for TrueHD are now built only with the TrueHDReferenceRender trait. A
shipped TrueHDObjectAudio build contains the bed split alone, and a
decoder that is asked for the exact path without the trait traps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
 / relay fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…or 9.1.6

With LibDTSX 3.0 the heights are removed from the 7.1 bed by the
standard's own constant fold rather than the gains the stream's metadata
states, and presentations with objects are refused, so they keep the
channel bridge and play as 7.1. DTS:X is now rendered only into the
layouts that carry its bed and four heights one to one (7.1.4, 9.1.6);
any other layout keeps the channel bridge with a log line saying so.
The decoder accepts the twelve standard channels and nothing else, so
no DTS:X channel is ever panned.

Docs, changelog and tests follow; the object tests are gone and the
alternate sample's verdict is now a refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…ion, DTS:X standard presentation only

LibTrueHD 3.0.0 (bed companion, interpolation out of the default build)
and LibDTSX 3.0.0 (standard presentation, constant height fold). Version
sweep, changelog folded, README notes the pbxproj traits form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…lakes fixed

The FIFO's backpressure applied only while a segment had bytes left over.
When a segment's last chunk was taken just under the ceiling, the next
request went out at once with the buffer already over it, which is what
made testFullBufferLetsTheCurrentSegmentFinishAndHoldsTheNextRequest
flaky on CI (large loopback chunks). The producer now waits for room
between segments either way; a new test builds the case deterministically
(fails 3/3 before, passes 3/3 after).

ItemSwapStillTests' fixture writer counted a starved test task as a
stalled AVAssetWriter: both tracks were ready, but the loop had not run
for the 20 s on a loaded runner. A stall now also needs 1000 idle polls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…/feat/731-energy-map

7.40.0: TrueHD Atmos from the channel presentation, DTS:X standard presentation only (superuser404notfound#731, superuser404notfound#744)
…r reached the track

A TrueHD track that starts after the probe window (John Wick: Chapter 2, TrueHD from 22 s,
45.8 MB in) leaves find_stream_info without a decoded frame, so the stream keeps
AV_PROFILE_UNKNOWN and objectAudioRendering played it through the channel bridge. When
object rendering is wanted and the profile is unknown, the engine now peeks the track's
packets where the first producer starts and reads the major sync the way mlpdec does (four
substreams, top bit of substream_info). Measured with the tvOS probe budget (16 MiB / 10 s):
from the start 2599 packets, 44.7 MB, 679 ms; on a resume 10 packets, 30 ms; APAC 7.1.4 in
both, where the same build without the check served E-AC-3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…hen the probe misses the track

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJcXkGQrMiBPixVEFTSBJS
…/fix/truehd-late-start

fix(audio): read TrueHD Atmos from the major sync when the probe never reached the track (7.40.1)
Brings the fork from upstream 7.38.1 (#22) to 7.40.1. Upstream 7.39.0
moved TrueHD Atmos and DTS:X object rendering (the APAC path), Profile 7
FEL composition and Dolby Vision P5 stills behind package traits that are
off by default, because those components may need third-party patent
licenses. The fork keeps upstream's defaults, so a host that does not set
the traits builds without LibTrueHD, LibDTSX or the object renderer, and
objectAudioRendering keeps the channel bridge.

Conflicts, resolved by keeping both sides:

- ci.yml: upstream's 50 min timeout, the fork's split of the
  authorization suites.
- HLSOriginRelay: both sides added the http/https origin check; upstream's
  form is kept.
- PlayerState `.bridged` and the docs/api.md row: upstream's trait wording
  with the fork's correction that only FLAC keeps the bed lossless.
- docs/api.md objectAudioRendering row: upstream's text, plus the
  `ObjectAudioRendering` reference PublicAPIDocumentationTests needs.
- docs/cli.md: upstream's text with the fork's apac.31.02 level for
  8 channels; docs/formats.md: upstream's text, unbalanced parenthesis
  fixed.
- DocumentedConstantsTests: upstream's trait gate, plus the fork's 8 and
  10 channel APAC CODECS checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@silo-triage silo-triage Bot added the size/XXXL 3000+ added + deleted lines (all files); change volume, not priority. label Oct 19, 2026
@coderabbitai

coderabbitai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 267 files, which is 167 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5efb4872-3fc7-4a4f-bf81-682cd485bdd4
📥 Commits

Reviewing files that changed from the base of the PR and between b071221 and 898946d.

⛔ Files ignored due to path filters: 2
  • Package.resolved is excluded by !**/Package.resolved
  • Probes/DolbyVisionPulse/Device/DolbyVisionPulse.xcodeproj/project.xcworkspace/contents.xcworkspacedata is excluded by !**/*.xcworkspace/contents.xcworkspacedata
📒 Files selected for processing: 267
  • .github/workflows/ci.yml
  • .github/workflows/release-dmg.yml
  • CHANGELOG.md
  • Examples/DemoPlayerMac/Scripts/build-dmg.sh
  • Examples/README.md
  • Package.swift
  • Probes/AllTraits/Package.swift
  • Probes/AllTraits/Sources/AetherEngineAllTraits/AllTraits.swift
  • Probes/DolbyVisionPulse/.gitignore
  • Probes/DolbyVisionPulse/Device/DolbyVisionPulse.xcodeproj/project.pbxproj
  • Probes/DolbyVisionPulse/Device/DolbyVisionPulse.xcodeproj/xcshareddata/xcschemes/DolbyVisionPulse.xcscheme
  • Probes/DolbyVisionPulse/Device/Host/DolbyVisionPulseHostApp.swift
  • Probes/DolbyVisionPulse/Device/Host/Info.plist
  • Probes/DolbyVisionPulse/Device/Host/PulseLog.swift
  • Probes/DolbyVisionPulse/Device/Host/PulseVideoToolbox.swift
  • Probes/DolbyVisionPulse/Device/PulseEngine/Package.swift
  • Probes/DolbyVisionPulse/Device/PulseEngine/Sources/PulseEngine/PulseEngine.swift
  • Probes/DolbyVisionPulse/Device/project.yml
  • Probes/DolbyVisionPulse/README.md
  • Probes/DolbyVisionPulse/Scripts/analyze.py
  • Probes/DolbyVisionPulse/Scripts/build.sh
  • Probes/DolbyVisionPulse/Scripts/check_el.py
  • Probes/DolbyVisionPulse/Scripts/cut.py
  • Probes/DolbyVisionPulse/Scripts/dump_attachments.swift
  • Probes/DolbyVisionPulse/Scripts/fel_count.py
  • Probes/DolbyVisionPulse/Scripts/gen_pulse.py
  • Probes/DolbyVisionPulse/Scripts/report.py
  • Probes/DolbyVisionPulse/Scripts/run_tv.sh
  • Probes/DolbyVisionPulse/Scripts/serve.py
  • Probes/DolbyVisionPulse/Scripts/sim_recording.py
  • README.md
  • Scripts/test-h264-partial-composition-controls.sh
  • Scripts/test-software-packet-read-ahead.sh
  • Scripts/tests/H264PartialCompositionRuntimeStandalone.swift
  • Scripts/tests/H264PartialCompositionStandalone.swift
  • Scripts/tests/make-missing-head-fixture.py
  • Sources/AetherEngine/AetherEngine+Diagnostics.swift
  • Sources/AetherEngine/AetherEngine+HeldPicture.swift
  • Sources/AetherEngine/AetherEngine+Live.swift
  • Sources/AetherEngine/AetherEngine+Loading.swift
  • Sources/AetherEngine/AetherEngine+OptionalComponents.swift
  • Sources/AetherEngine/AetherEngine+Probe.swift
  • Sources/AetherEngine/AetherEngine+ReloadWithOptions.swift
  • Sources/AetherEngine/AetherEngine+RequestHeaders.swift
  • Sources/AetherEngine/AetherEngine+ScrubThumbnail.swift
  • Sources/AetherEngine/AetherEngine+SoftwarePathEscalation.swift
  • Sources/AetherEngine/AetherEngine+Subtitles.swift
  • Sources/AetherEngine/AetherEngine+Version.swift
  • Sources/AetherEngine/AetherEngine.swift
  • Sources/AetherEngine/Audio/AtmosConfirmationWorker.swift
  • Sources/AetherEngine/Audio/AudioBridge.swift
  • Sources/AetherEngine/Audio/AudioOutput.swift
  • Sources/AetherEngine/Audio/AudioTranscodingBridge.swift
  • Sources/AetherEngine/Audio/DTSXConfirmation.swift
  • Sources/AetherEngine/Audio/FLACStreamInfo.swift
  • Sources/AetherEngine/Audio/Spatial/AllocentricPanner.swift
  • Sources/AetherEngine/Audio/Spatial/BedLevelMeter.swift
  • Sources/AetherEngine/Audio/Spatial/BedSplitter.swift
  • Sources/AetherEngine/Audio/Spatial/DTSXObjectAudioDecoder.swift
  • Sources/AetherEngine/Audio/Spatial/ObjectAudioActivity.swift
  • Sources/AetherEngine/Audio/Spatial/ObjectAudioDecoding.swift
  • Sources/AetherEngine/Audio/Spatial/ObjectAudioRenderer.swift
  • Sources/AetherEngine/Audio/Spatial/ObjectAudioRendering.swift
  • Sources/AetherEngine/Audio/Spatial/ObjectMetadataHold.swift
  • Sources/AetherEngine/Audio/Spatial/SpatialAudioBridge.swift
  • Sources/AetherEngine/Audio/Spatial/SpatialSpeakerLayout.swift
  • Sources/AetherEngine/Audio/Spatial/TrueHDMajorSync.swift
  • Sources/AetherEngine/Audio/Spatial/TrueHDObjectAudioDecoder.swift
  • Sources/AetherEngine/Decoder/A53SEIParser.swift
  • Sources/AetherEngine/Decoder/DualLayerPictureAlignment.swift
  • Sources/AetherEngine/Decoder/DualLayerRouteDecision.swift
  • Sources/AetherEngine/Decoder/DualLayerVideoDecoder.swift
  • Sources/AetherEngine/Decoder/FrameSideDataRing.swift
  • Sources/AetherEngine/Decoder/HardwareVideoDecoder.swift
  • Sources/AetherEngine/Decoder/OrderedDelivery.swift
  • Sources/AetherEngine/Decoder/VideoDecoderTypes.swift
  • Sources/AetherEngine/Decoder/VideoRoutingPolicy.swift
  • Sources/AetherEngine/Demuxer/AVIOProvider.swift
  • Sources/AetherEngine/Demuxer/AVIOReader.swift
  • Sources/AetherEngine/Demuxer/Demuxer.swift
  • Sources/AetherEngine/Demuxer/RequestHeaderStore.swift
  • Sources/AetherEngine/Diagnostics/EngineLog.swift
  • Sources/AetherEngine/Diagnostics/FelResidualMeasure.swift
  • Sources/AetherEngine/Diagnostics/LiveTelemetry.swift
  • Sources/AetherEngine/Diagnostics/LiveTelemetrySampler.swift
  • Sources/AetherEngine/Disc/DiscDirectory.swift
  • Sources/AetherEngine/Disc/DiscReader.swift
  • Sources/AetherEngine/FrameExtractor/AetherEngine+FrameExtractor.swift
  • Sources/AetherEngine/FrameExtractor/DolbyVisionStillConverter.swift
  • Sources/AetherEngine/FrameExtractor/FrameDecodeContext.swift
  • Sources/AetherEngine/FrameExtractor/FrameExtractor.swift
  • Sources/AetherEngine/IO/HLSIngest/HLSLiveIngestReader.swift
  • Sources/AetherEngine/IO/HLSIngest/HLSVODIngestReader.swift
  • Sources/AetherEngine/IO/HLSIngest/PersistentHTTP1Transport.swift
  • Sources/AetherEngine/Native/AirPlayPlaylistDecision.swift
  • Sources/AetherEngine/Native/MasterFallbackDecision.swift
  • Sources/AetherEngine/Native/NativeAVPlayerHost.swift
  • Sources/AetherEngine/Native/SoftwareLiveJoinPolicy.swift
  • Sources/AetherEngine/Native/SoftwarePacketCoverage.swift
  • Sources/AetherEngine/Native/SoftwarePacketDiskFIFO.swift
  • Sources/AetherEngine/Native/SoftwarePacketMemoryFIFO.swift
  • Sources/AetherEngine/Native/SoftwarePacketReadAhead.swift
  • Sources/AetherEngine/Native/SoftwarePacketStore.swift
  • Sources/AetherEngine/Native/SoftwarePathEscalation.swift
  • Sources/AetherEngine/Native/SoftwarePlaybackHost.swift
  • Sources/AetherEngine/Native/SoftwareReadAdmission.swift
  • Sources/AetherEngine/Native/SoftwareVideoPacketCoverage.swift
  • Sources/AetherEngine/Network/HLSLocalServer.swift
  • Sources/AetherEngine/Network/HLSOriginRelay.swift
  • Sources/AetherEngine/PlaybackErrorInfo.swift
  • Sources/AetherEngine/PlayerState.swift
  • Sources/AetherEngine/Renderer/PresentationFloor.swift
  • Sources/AetherEngine/Renderer/SampleBufferRenderer.swift
  • Sources/AetherEngine/SeekEvent.swift
  • Sources/AetherEngine/Video/AetherEngine+DolbyVisionTest.swift
  • Sources/AetherEngine/Video/AetherEngine+FelProbe.swift
  • Sources/AetherEngine/Video/CodecRoutePolicy.swift
  • Sources/AetherEngine/Video/DolbyVisionRecordAudit.swift
  • Sources/AetherEngine/Video/DoviDisplayMetadata.swift
  • Sources/AetherEngine/Video/DoviDisplayMetadataDebug.swift
  • Sources/AetherEngine/Video/DoviRpuConverter+Probe.swift
  • Sources/AetherEngine/Video/DoviRpuConverter.swift
  • Sources/AetherEngine/Video/DoviRpuGuard.swift
  • Sources/AetherEngine/Video/DoviRpuSurvey.swift
  • Sources/AetherEngine/Video/H264CompositionOffsetRepair.swift
  • Sources/AetherEngine/Video/H264ConfigurationTracker.swift
  • Sources/AetherEngine/Video/H264MatroskaSlotPermutation.swift
  • Sources/AetherEngine/Video/H264PartialCompositionRepair.swift
  • Sources/AetherEngine/Video/H264PartialCompositionRepairSession.swift
  • Sources/AetherEngine/Video/H264SPS.swift
  • Sources/AetherEngine/Video/HEVCPictureOrderCount.swift
  • Sources/AetherEngine/Video/HLSSegmentProducer.swift
  • Sources/AetherEngine/Video/HLSVideoEngine+AudioRoute.swift
  • Sources/AetherEngine/Video/HLSVideoEngine+LiveReopen.swift
  • Sources/AetherEngine/Video/HLSVideoEngine+SegmentPlanning.swift
  • Sources/AetherEngine/Video/HLSVideoEngine+SpatialAudio.swift
  • Sources/AetherEngine/Video/HLSVideoEngine.swift
  • Sources/AetherEngine/Video/IFrameJPEGBuilder.swift
  • Sources/AetherEngine/Video/IFrameRendition.swift
  • Sources/AetherEngine/Video/IFrameRenditionEligibility.swift
  • Sources/AetherEngine/Video/LivePacketReplayGuard.swift
  • Sources/AetherEngine/Video/MP4SegmentMuxer.swift
  • Sources/AetherEngine/Video/Metal/DoviFelComposer.swift
  • Sources/AetherEngine/Video/Metal/DoviMetalParams.swift
  • Sources/AetherEngine/Video/Metal/DoviShaderSource.swift
  • Sources/AetherEngine/Video/VTCapabilityProbe.swift
  • Sources/AetherEngine/Video/VideoConfigRecord+InBand.swift
  • Sources/AetherEngine/Video/VideoSegmentProvider.swift
  • Sources/aetherctl/DvSurvey.swift
  • Sources/aetherctl/FelTest.swift
  • Sources/aetherctl/PlaybackCmd.swift
  • Sources/aetherctl/main.swift
  • Tests/AetherEngineTests/ActiveVideoDecoderTests.swift
  • Tests/AetherEngineTests/AirPlayPlaylistDecisionTests.swift
  • Tests/AetherEngineTests/AudioLanguageSelectionTests.swift
  • Tests/AetherEngineTests/BedSplitterTests.swift
  • Tests/AetherEngineTests/CredentialRefusalTests.swift
  • Tests/AetherEngineTests/DTSXConfirmationTests.swift
  • Tests/AetherEngineTests/DTSXObjectMetadataMappingTests.swift
  • Tests/AetherEngineTests/DTSXSampleTests.swift
  • Tests/AetherEngineTests/DVDCellTimelineTests.swift
  • Tests/AetherEngineTests/DVDFolderTests.swift
  • Tests/AetherEngineTests/DetourBlockIdleReleaseTests.swift
  • Tests/AetherEngineTests/DetourOriginTicketTests.swift
  • Tests/AetherEngineTests/DocumentedConstantsTests.swift
  • Tests/AetherEngineTests/DolbyVisionColorTests.swift
  • Tests/AetherEngineTests/DolbyVisionRecordAuditTests.swift
  • Tests/AetherEngineTests/DoviComposeAsyncTests.swift
  • Tests/AetherEngineTests/DoviComposedFrameRpuTests.swift
  • Tests/AetherEngineTests/DoviCpuModelTests.swift
  • Tests/AetherEngineTests/DoviCrcValidFuzzTests.swift
  • Tests/AetherEngineTests/DoviDisplayMetadataTests.swift
  • Tests/AetherEngineTests/DoviDitherTests.swift
  • Tests/AetherEngineTests/DoviElBitDepthTests.swift
  • Tests/AetherEngineTests/DoviEnhancementUpsamplerTests.swift
  • Tests/AetherEngineTests/DoviFelFuzzTests.swift
  • Tests/AetherEngineTests/DoviFelMathTests.swift
  • Tests/AetherEngineTests/DoviFelRealFileTests.swift
  • Tests/AetherEngineTests/DoviGpuAdversarialTests.swift
  • Tests/AetherEngineTests/DoviGpuCurveSweepTests.swift
  • Tests/AetherEngineTests/DoviGpuEdgeCaseTests.swift
  • Tests/AetherEngineTests/DoviLayoutProbeTests.swift
  • Tests/AetherEngineTests/DoviPinTestTests.swift
  • Tests/AetherEngineTests/DoviRealFileExportTests.swift
  • Tests/AetherEngineTests/DoviRpuConverterTests.swift
  • Tests/AetherEngineTests/DoviRpuGuardTests.swift
  • Tests/AetherEngineTests/DoviRpuSurveyResultTests.swift
  • Tests/AetherEngineTests/DoviShaderSyncTests.swift
  • Tests/AetherEngineTests/DualLayerEdgeCaseTests.swift
  • Tests/AetherEngineTests/DualLayerGpuSlotWaitTests.swift
  • Tests/AetherEngineTests/DualLayerPictureAlignmentSyntheticTests.swift
  • Tests/AetherEngineTests/DualLayerPictureAlignmentTests.swift
  • Tests/AetherEngineTests/DualLayerRouteAudioTests.swift
  • Tests/AetherEngineTests/DualLayerRouteDecisionTests.swift
  • Tests/AetherEngineTests/DualLayerSeekRealignmentTests.swift
  • Tests/AetherEngineTests/DualLayerStreamClassificationTests.swift
  • Tests/AetherEngineTests/DualLayerVideoDecoderTests.swift
  • Tests/AetherEngineTests/ErrorEndedReconnectTests.swift
  • Tests/AetherEngineTests/FrameDecodeContextHardwareTests.swift
  • Tests/AetherEngineTests/FrameSideDataRingTests.swift
  • Tests/AetherEngineTests/H264CompositionOffsetRepairTests.swift
  • Tests/AetherEngineTests/H264ConfigurationTrackerTests.swift
  • Tests/AetherEngineTests/HEVCCodecStringTests.swift
  • Tests/AetherEngineTests/HEVCPictureOrderCountTests.swift
  • Tests/AetherEngineTests/HEVCSyntheticBitstream.swift
  • Tests/AetherEngineTests/HLSLiveIngestRefusalTests.swift
  • Tests/AetherEngineTests/HLSOriginRelayTests.swift
  • Tests/AetherEngineTests/IFrameJPEGBuilderTests.swift
  • Tests/AetherEngineTests/IFramePlaylistTextTests.swift
  • Tests/AetherEngineTests/IFrameRenditionEligibilityTests.swift
  • Tests/AetherEngineTests/IFrameSessionTests.swift
  • Tests/AetherEngineTests/InBandParameterSetRebuildTests.swift
  • Tests/AetherEngineTests/Issue220BoundedRangeTests.swift
  • Tests/AetherEngineTests/Issue309SilentTransportDeathTests.swift
  • Tests/AetherEngineTests/Issue396SilentAudioBridgeTests.swift
  • Tests/AetherEngineTests/Issue410SourceStallVisibilityTests.swift
  • Tests/AetherEngineTests/Issue460ReloadWithOptionsTests.swift
  • Tests/AetherEngineTests/Issue511MatroskaCodingOrderTests.swift
  • Tests/AetherEngineTests/Issue613PacketCoverageTests.swift
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift
  • Tests/AetherEngineTests/Issue764FLACSmallBlockTests.swift
  • Tests/AetherEngineTests/Issue782RestartReanchorTests.swift
  • Tests/AetherEngineTests/Issue783PeakBandwidthTests.swift
  • Tests/AetherEngineTests/ItemSwapStillTests.swift
  • Tests/AetherEngineTests/LivePacketReplayGuardTests.swift
  • Tests/AetherEngineTests/LiveReopenAfterTeardownTests.swift
  • Tests/AetherEngineTests/LiveRequestHeaderTests.swift
  • Tests/AetherEngineTests/LiveStreamRefusalRetryTests.swift
  • Tests/AetherEngineTests/LoadOptionsTests.swift
  • Tests/AetherEngineTests/MasterFallbackDecisionTests.swift
  • Tests/AetherEngineTests/NALUnitChainTests.swift
  • Tests/AetherEngineTests/ObjectMetadataHoldTests.swift
  • Tests/AetherEngineTests/OptionalComponentFallbackTests.swift
  • Tests/AetherEngineTests/OrderedDeliveryTests.swift
  • Tests/AetherEngineTests/PlaybackErrorInfoTests.swift
  • Tests/AetherEngineTests/PresentationFloorTests.swift
  • Tests/AetherEngineTests/ResolvedURLInvalidationTests.swift
  • Tests/AetherEngineTests/SampleBufferRendererLookaheadTests.swift
  • Tests/AetherEngineTests/ServedFromMemoryProgressTests.swift
  • Tests/AetherEngineTests/SoftwareDecoderSelectionTests.swift
  • Tests/AetherEngineTests/SoftwareLiveJoinPlacementTests.swift
  • Tests/AetherEngineTests/SoftwarePacketMemoryFIFOTests.swift
  • Tests/AetherEngineTests/SoftwarePacketReadAheadAudioCoverageTests.swift
  • Tests/AetherEngineTests/SoftwarePathEscalationTests.swift
  • Tests/AetherEngineTests/SoftwareRepositionResultTests.swift
  • Tests/AetherEngineTests/SoftwareResidentRangesTests.swift
  • Tests/AetherEngineTests/SourceOpenRecoveryTests.swift
  • Tests/AetherEngineTests/SpatialAudioBridgeTests.swift
  • Tests/AetherEngineTests/SpatialObjectRenderingTests.swift
  • Tests/AetherEngineTests/StaleSessionCallbackTests.swift
  • Tests/AetherEngineTests/SubtitleFrameCompositorTests.swift
  • Tests/AetherEngineTests/SupersededProducerReportTests.swift
  • Tests/AetherEngineTests/SupersededPumpExitTests.swift
  • Tests/AetherEngineTests/Support/DoviTestSupport.swift
  • Tests/AetherEngineTests/Support/LoopbackListener.swift
  • Tests/AetherEngineTests/Support/OffCooperativePool.swift
  • Tests/AetherEngineTests/TrueHDAtmosSampleTests.swift
  • Tests/AetherEngineTests/TrueHDMajorSyncTests.swift
  • Tests/AetherEngineTests/TrueHDObjectMetadataMappingTests.swift
  • Tests/AetherEngineTests/UndocumentedDolbyVisionKeysTests.swift
  • Tests/AetherEngineTests/VideoPrerollTests.swift
  • Tests/AetherEngineTests/VideoRoutingPolicyTests.swift
  • docs/api.md
  • docs/architecture.md
  • docs/cli.md
  • docs/dolby-vision-fel.md
  • docs/formats.md

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@silo-kody

silo-kody Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

Comment on lines +205 to +208
rows = [r for r, k in zip(rows, ok) if k]

# Split into runs where the clip frame index restarts.
runs, cur = [], [rows[0]]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug critical

IndexError occurs when the consistency filter removes every decoded row and run splitting indexes rows[0], causing short recordings or recordings with only inconsistent counter reads to bypass the existing actionable no-decodable-frames diagnostic. Check for an empty filtered result before constructing runs.

rows = [r for r, k in zip(rows, ok) if k]
if not rows:
    sys.exit(f"no consistent counter frames ({bad} rejected): check framing, exposure, fiducials in view")

# Split into runs where the clip frame index restarts.
runs, cur = [], [rows[0]]
Prompt for LLM

File Probes/DolbyVisionPulse/Scripts/analyze.py:

Line 205 to 208:

IndexError occurs when the consistency filter removes every decoded row and run splitting indexes rows[0], causing short recordings or recordings with only inconsistent counter reads to bypass the existing actionable no-decodable-frames diagnostic. Check for an empty filtered result before constructing runs.

Suggested Code:

    rows = [r for r, k in zip(rows, ok) if k]
    if not rows:
        sys.exit(f"no consistent counter frames ({bad} rejected): check framing, exposure, fiducials in view")

    # Split into runs where the clip frame index restarts.
    runs, cur = [], [rows[0]]

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: if the consistency filter drops every row, rows[0] raises IndexError instead of the no-decodable-frames message. This script comes unchanged from upstream 7.37.0 (73955ad, the Dolby Vision pulse probe), and this PR only merges upstream, so it stays as upstream has it. The probe is measurement tooling outside the AetherEngine library, so no app ships it. Keeping it out of the sync so the fork doesn't drift from upstream.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood—leave analyze.py unchanged in this sync. The bug is real, but it affects upstream-only measurement tooling, not the shipped library, and fixing it here would introduce fork divergence. The original critical severity is not warranted for this scope; any fix can be pursued upstream separately.

Comment on lines +26 to +27
raw = subprocess.check_output(cmd)
return np.frombuffer(raw, np.uint8).reshape(-1, 540, 960).astype(np.float32) / 255.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Performance high

Excessive peak memory usage in clip_luma() results from capturing all 1,584 decoded 960×540 frames and converting the entire video to float32 before simulation: the documented 66-second fixture produces a 3.28 GB float array alongside an 821 MB captured byte buffer and conversion temporaries. Stream decoded frames and retain only the current frame needed for the 30 fps output.

Prompt for LLM

File Probes/DolbyVisionPulse/Scripts/sim_recording.py:

Line 26 to 27:

Excessive peak memory usage in clip_luma() results from capturing all 1,584 decoded 960×540 frames and converting the entire video to float32 before simulation: the documented 66-second fixture produces a 3.28 GB float array alongside an 821 MB captured byte buffer and conversion temporaries. Stream decoded frames and retain only the current frame needed for the 30 fps output.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: decoding the whole clip into one float32 array peaks at several GB. Same as the other note: this is upstream's Dolby Vision pulse probe (7.37.0), unchanged by this merge, and measurement tooling no app builds or ships. Leaving it as upstream has it in this sync.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. The memory concern is valid, but this is unchanged upstream measurement tooling that no app builds or ships. It does not need to block this sync; leaving it unchanged is reasonable. Streaming the decoded frames can be addressed separately upstream.

Comment on lines +4255 to +4257
refusalStore.onCredentialRefusal = { [weak self] status in
Task { @MainActor [weak self] in
guard let self, self.sessionRequestHeaders === refusalStore else { return }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

Retain cycle on every load permanently retains each old store and its credential headers because the credential-refusal closure strongly captures refusalStore while RequestHeaderStore strongly retains the same closure in onRefusal. Capture refusalStore weakly and unwrap it before the identity check.

Prompt for LLM

File Sources/AetherEngine/AetherEngine.swift:

Line 4255 to 4257:

Retain cycle on every load permanently retains each old store and its credential headers because the credential-refusal closure strongly captures refusalStore while RequestHeaderStore strongly retains the same closure in onRefusal. Capture refusalStore weakly and unwrap it before the identity check.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: the closure captures refusalStore strongly and the store keeps the closure, so each load's store and its headers stay alive. This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. It does ship in Silo, so I'm taking it up as an upstream fix rather than a fork-only patch.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood—this is a confirmed bug deferred to upstream, not a false positive. Keeping the code unchanged preserves upstream parity for this merge, but the retain cycle still affects Silo until the fix lands.

The upstream fix should weakly capture refusalStore and unwrap it before the identity check. No changes are needed in this PR under that approach.

Comment on lines +427 to +432
renderer!.advance(frameCount: block.frameCount, updates: block.updates)
renderer!.snapshot(into: &gainSnapshot)
splitter!.setGains(gainSnapshot)
pendingFrames += splitter!.process(
elements: block.planes, bed: bed.planes, frameCount: block.frameCount,
output: output.planes.map { $0 + pendingFrames })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

Metadata timing error applies a TrueHD block's final position or gain state to earlier samples because advancing the renderer through the entire block before taking one gain snapshot discards the updates' frameOffset timing. Process spans ending at metadata updates and refresh the splitter's gains for each span.

Prompt for LLM

File Sources/AetherEngine/Audio/Spatial/SpatialAudioBridge.swift:

Line 427 to 432:

Metadata timing error applies a TrueHD block's final position or gain state to earlier samples because advancing the renderer through the entire block before taking one gain snapshot discards the updates' frameOffset timing. Process spans ending at metadata updates and refresh the splitter's gains for each span.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. It is compiled only with an opt-in package trait that Silo does not enable, so no Silo build contains it. (TrueHDObjectAudio).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. Since this code is unchanged from upstream and excluded from Silo builds by the disabled TrueHDObjectAudio trait, this should not block the sync PR.

The timing concern may still apply to builds that enable the trait, but it belongs in an upstream follow-up rather than a fork-specific change here. No changes are needed in this PR for this finding.

Comment on lines +551 to +556
let layers = DoviRpuConverter.separateDualLayers(
packet,
extractEL: !skipEL,
useCustomBlockSource: true,
extractParams: canCompose
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

NAL framing mismatch silently disables RPU/EL extraction and FEL composition for valid one- or two-byte-length HEVC samples because parsing assumes four-byte lengths instead of the size declared by hvcC. Resolve framing from the stream configuration and pass it to separateDualLayers, pictureAlignment, and the in-band parameter-set walkers.

Prompt for LLM

File Sources/AetherEngine/Decoder/DualLayerVideoDecoder.swift:

Line 551 to 556:

NAL framing mismatch silently disables RPU/EL extraction and FEL composition for valid one- or two-byte-length HEVC samples because parsing assumes four-byte lengths instead of the size declared by hvcC. Resolve framing from the stream configuration and pass it to separateDualLayers, pictureAlignment, and the in-band parameter-set walkers.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. It is compiled only with an opt-in package trait that Silo does not enable, so no Silo build contains it. (DualLayerDolbyVision).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. Since this code is unchanged from upstream and gated behind DualLayerDolbyVision, which Silo does not enable, the framing concern does not affect Silo’s current builds.

I would treat it as a potential upstream issue for trait-enabled builds, not a required fix for this sync PR. Keeping the upstream code unchanged here is reasonable.

let closedAfterEnsure = isClosed
lock.unlock()
if !closedAfterEnsure {
el?.decode(packet: elPacket, epoch: epoch)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

Race condition allows an old packet into post-seek sessions when a flush occurs between the shared-state check and layer submissions because forwarding epoch instead of entryEpoch disables the sub-decoders' stale-feed checks for the DVR reseed caller, which explicitly passes nil. Pass the captured entryEpoch to both layer decoders so their synchronized epoch gates reject stale packets.

el?.decode(packet: elPacket, epoch: entryEpoch)
Prompt for LLM

File Sources/AetherEngine/Decoder/DualLayerVideoDecoder.swift:

Line 608:

Race condition allows an old packet into post-seek sessions when a flush occurs between the shared-state check and layer submissions because forwarding epoch instead of entryEpoch disables the sub-decoders' stale-feed checks for the DVR reseed caller, which explicitly passes nil. Pass the captured entryEpoch to both layer decoders so their synchronized epoch gates reject stale packets.

Suggested Code:

el?.decode(packet: elPacket, epoch: entryEpoch)

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. It is compiled only with an opt-in package trait that Silo does not enable, so no Silo build contains it. (DualLayerDolbyVision).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood—leaving this unchanged is reasonable for an upstream-sync PR, especially since Silo does not enable DualLayerDolbyVision and therefore does not compile this code.

The race concern still applies to trait-enabled consumers, but it belongs in an upstream report rather than a fork-only patch here. I would treat it as non-blocking for this merge.

winCond.lock()
connGeneration &+= 1
let generation = connGeneration
connHeaderRevision = requestHeaderStore?.revision ?? 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

Race condition between line 3368 and request construction can stamp new credentials with an old connection revision because the code captures the revision separately from the headers, making a new token's refusal appear to concern an already-replaced credential. Capture the header dictionary and revision atomically and use that snapshot throughout request and redirect-delegate construction.

Prompt for LLM

File Sources/AetherEngine/Demuxer/AVIOReader.swift:

Line 3368:

Race condition between line 3368 and request construction can stamp new credentials with an old connection revision because the code captures the revision separately from the headers, making a new token's refusal appear to concern an already-replaced credential. Capture the header dictionary and revision atomically and use that snapshot throughout request and redirect-delegate construction.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. Plausible: the revision is read apart from the headers it describes. The effect is a refusal attributed to the wrong header revision. Upstream follow-up rather than a fork-only change in the sync.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed—leave the upstream code unchanged in this sync PR and treat this as a non-blocking upstream follow-up.

The race remains plausible: reading the revision separately from the headers could attribute a refusal to the wrong credential revision. The upstream fix should capture headers and revision atomically and use that snapshot for both request and redirect-delegate construction.

I can create a tracking issue if you’d like; nothing has been changed.

Comment on lines +72 to +74
if announcedRevision != revision {
announcedRevision = revision
callback = onRefusal

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

Stale credential notification occurs when noteCredentialRefusal announces an obsolete credential's 401/403 after replace because it checks only the previously recorded refusal, not revisionValue, causing an in-flight old request to emit sourceAuthorizationRefused for a renewed token. Suppress the callback when the supplied revision differs from the current revision, while retaining historical refusal information if recovery needs it.

Prompt for LLM

File Sources/AetherEngine/Demuxer/RequestHeaderStore.swift:

Line 72 to 74:

Stale credential notification occurs when noteCredentialRefusal announces an obsolete credential's 401/403 after replace because it checks only the previously recorded refusal, not revisionValue, causing an in-flight old request to emit sourceAuthorizationRefused for a renewed token. Suppress the callback when the supplied revision differs from the current revision, while retaining historical refusal information if recovery needs it.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: an in-flight request answered after replace still announces a refusal for the old revision. This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. For Silo the effect is one extra token refresh (noted in Silo-Server/silo-apple#838). Upstream follow-up.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. The bug is confirmed, but leaving it unchanged in this upstream-sync PR is intentional to preserve parity with 7.40.1. For Silo, the impact is one extra token refresh, noted in Silo-Server/silo-apple#838. The fix belongs in an upstream follow-up, not as a blocker for this merge.

Comment on lines +638 to +641
guard let previous, pts > previous, stream.numerator > 0, stream.denominator > 0,
Double(pts - previous) * Double(stream.numerator) / Double(stream.denominator)
<= Self.maxAudioSuccessorGapSeconds else { return }
audioCoverage.insert(pts: previous, duration: pts - previous)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug critical

Integer overflow in unchecked pts - previous arithmetic traps before the half-second gap check when two valid, increasing Int64 timestamps differ by more than Int64.max; stored packets copy source PTS values without restricting this range. Use subtractingReportingOverflow and reject overflow before converting or inserting the interval.

guard let previous, pts > previous, stream.numerator > 0, stream.denominator > 0 else { return }
let (gap, overflow) = pts.subtractingReportingOverflow(previous)
guard !overflow,
      Double(gap) * Double(stream.numerator) / Double(stream.denominator)
        <= Self.maxAudioSuccessorGapSeconds else { return }
audioCoverage.insert(pts: previous, duration: gap)
Prompt for LLM

File Sources/AetherEngine/Native/SoftwarePacketReadAhead.swift:

Line 638 to 641:

Integer overflow in unchecked pts - previous arithmetic traps before the half-second gap check when two valid, increasing Int64 timestamps differ by more than Int64.max; stored packets copy source PTS values without restricting this range. Use subtractingReportingOverflow and reject overflow before converting or inserting the interval.

Suggested Code:

guard let previous, pts > previous, stream.numerator > 0, stream.denominator > 0 else { return }
let (gap, overflow) = pts.subtractingReportingOverflow(previous)
guard !overflow,
      Double(gap) * Double(stream.numerator) / Double(stream.denominator)
        <= Self.maxAudioSuccessorGapSeconds else { return }
audioCoverage.insert(pts: previous, duration: gap)

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed for timestamps far enough apart to overflow, which only crafted media carries. This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. It ships in Silo, so I'm taking it up as an upstream fix.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood—this is a confirmed overflow bug, deferred to upstream rather than fixed in this synchronization PR. Keeping the merge aligned with upstream doesn’t eliminate the crafted-media crash risk; subtractingReportingOverflow remains the appropriate fix.

I can create a tracking issue linking this finding to PR #23 if you’d like.

Comment on lines +201 to +203
if elType == nil { elType = DoviRpuConverter.enhancementLayerType(packet) }
// extractEL: false; the survey never touches pixels, so the EL is dead weight.
let layers = DoviRpuConverter.separateDualLayers(packet, extractEL: false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

NAL framing mismatch causes both survey RPU walkers to interpret Annex-B HEVC packets as four-byte-length-prefixed samples, skipping their RPUs and leaving TS/disc surveys empty after scanning the title. Resolve framing from the video stream's extradata and pass it to both calls, as doviProbe already does.

Prompt for LLM

File Sources/AetherEngine/Video/DoviRpuSurvey.swift:

Line 201 to 203:

NAL framing mismatch causes both survey RPU walkers to interpret Annex-B HEVC packets as four-byte-length-prefixed samples, skipping their RPUs and leaving TS/disc surveys empty after scanning the title. Resolve framing from the video stream's extradata and pass it to both calls, as doviProbe already does.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. This is the aetherctl dvsurvey diagnostic; no app calls it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood—keeping this unchanged is reasonable for an upstream-sync PR, especially since aetherctl dvsurvey is diagnostic-only and no app calls it.

The framing concern still applies to surveys of Annex-B inputs, but it is not an app playback regression or a blocker for this merge. My high-severity rating overstated the impact; any fix belongs upstream rather than as an additional fork patch here.

func appendArray(nalUnitType: UInt8, nal: [UInt8]) {
hvcC.append(0x80 | (nalUnitType & 0x3F))
hvcC.append(0); hvcC.append(1)
let nl = UInt16(nal.count)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Security critical

Integer conversion trap in UInt16(nal.count) terminates playback during hvcC rebuilding when a media-controlled VPS, SPS, or PPS exceeds 65,535 bytes; a crafted four-byte-length-prefixed packet containing an oversized type-32/33/34 NAL passes collect. Reject oversized parameter sets and propagate reconstruction failure instead of performing the trapping conversion.

Prompt for LLM

File Sources/AetherEngine/Video/VideoConfigRecord+InBand.swift:

Line 57:

Integer conversion trap in UInt16(nal.count) terminates playback during hvcC rebuilding when a media-controlled VPS, SPS, or PPS exceeds 65,535 bytes; a crafted four-byte-length-prefixed packet containing an oversized type-32/33/34 NAL passes collect. Reject oversized parameter sets and propagate reconstruction failure instead of performing the trapping conversion.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: a VPS, SPS or PPS over 65,535 bytes traps at UInt16(nal.count); only a crafted file carries one. This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. It ships in Silo, so I'm taking it up as an upstream fix.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood—leave the upstream code unchanged in this merge and pursue the fix upstream. The crash remains reachable in Silo with crafted media; keeping the fork aligned defers the fix rather than resolving the finding.

The upstream fix should reject parameter sets larger than UInt16.max and propagate reconstruction failure, with regression tests covering oversized VPS, SPS, and PPS inputs.

If useful, I can create a tracking issue for the upstream follow-up.

return s
}

func denormalize(_ n: Double) -> Int { Int((n * blMax * scale).rounded(.toNearestOrEven)) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug medium

Reference-model mismatch occurs because Model.denormalize omits the pre-residual clamp in the production shader's dovi_denormalize, producing different results when residuals bring negative or above-full-scale predictions back into range. Clamp the rounded prediction to 0...Int(p.vdrMax) so modelMatchesGpu, errorAttribution, and the loss census use the actual composition equation.

func denormalize(_ n: Double) -> Int {
    let prediction = Int((n * blMax * scale).rounded(.toNearestOrEven))
    return min(max(prediction, 0), Int(p.vdrMax))
}
Prompt for LLM

File Tests/AetherEngineTests/DoviCpuModelTests.swift:

Line 96:

Reference-model mismatch occurs because Model.denormalize omits the pre-residual clamp in the production shader's dovi_denormalize, producing different results when residuals bring negative or above-full-scale predictions back into range. Clamp the rounded prediction to 0...Int(p.vdrMax) so modelMatchesGpu, errorAttribution, and the loss census use the actual composition equation.

Suggested Code:

func denormalize(_ n: Double) -> Int {
    let prediction = Int((n * blMax * scale).rounded(.toNearestOrEven))
    return min(max(prediction, 0), Int(p.vdrMax))
}

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. Test-only, for the DualLayerDolbyVision composer.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood—keeping this unchanged is reasonable for an upstream-sync PR. The concern is limited to the DualLayerDolbyVision test reference model, not a production-code defect demonstrated here. Any correction should be proposed upstream rather than added as a fork-only patch in this merge.


final class Sink: @unchecked Sendable {
private let lock = NSLock()
private(set) var pts: [Double] = []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

Data race occurs when sessionRebuildKeepsTheEpoch polls s.sink.pts.count before draining delivery because Sink.pts exposes an unlocked Array getter while take mutates the same array under a lock on OrderedDelivery's asynchronous dispatch queue, risking undefined behavior or Thread Sanitizer failures. Keep storage private and return a snapshot under the same lock.

private var storedPTS: [Double] = []
var pts: [Double] {
    lock.lock()
    defer { lock.unlock() }
    return storedPTS
}
// In take and reset, mutate storedPTS while holding lock.
Prompt for LLM

File Tests/AetherEngineTests/DoviFelRealFileTests.swift:

Line 111:

Data race occurs when sessionRebuildKeepsTheEpoch polls s.sink.pts.count before draining delivery because Sink.pts exposes an unlocked Array getter while take mutates the same array under a lock on OrderedDelivery's asynchronous dispatch queue, risking undefined behavior or Thread Sanitizer failures. Keep storage private and return a snapshot under the same lock.

Suggested Code:

private var storedPTS: [Double] = []
var pts: [Double] {
    lock.lock()
    defer { lock.unlock() }
    return storedPTS
}
// In take and reset, mutate storedPTS while holding lock.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. Test-only, for the DualLayerDolbyVision route.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood—keeping this unchanged is reasonable for an upstream-sync PR. The potential race remains relevant when the DualLayerDolbyVision tests run, but it belongs in an upstream follow-up rather than a fork-specific change here. I wouldn’t treat this test-only finding as a blocker for #23.

defer { reader.close() }
XCTAssertFalse(server.completed.contains("/seg0.ts"), "the verdict did not wait for the whole segment")

let stream = Self.drain(reader)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules high

Cooperative-executor blocking occurs because Self.drain makes synchronous reader.read calls until the stream ends while the first segment is deliberately still downloading. Run the drain on a GCD worker and bridge completion with a checked continuation to avoid occupying the async test's cooperative-executor thread.

Also found in:

  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:281-281
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:143-143
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:206-206
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:308-308
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:95-95
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:87-87
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:147-147
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:202-202
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:276-276
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:303-303
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:172-172
  • Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:177-177

Kody rule violation: Move blocking decodes off Swift's cooperative executor

let stream: [UInt8] = await withCheckedContinuation { continuation in
    DispatchQueue.global(qos: .utility).async {
        continuation.resume(returning: Self.drain(reader))
    }
}
Prompt for LLM

File Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:

Line 117:

Cooperative-executor blocking occurs because Self.drain makes synchronous reader.read calls until the stream ends while the first segment is deliberately still downloading. Run the drain on a GCD worker and bridge completion with a checked continuation to avoid occupying the async test's cooperative-executor thread.

**Also found in:**
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:281-281`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:143-143`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:206-206`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:308-308`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:95-95`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:87-87`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:147-147`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:202-202`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:276-276`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:303-303`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:172-172`
- `Tests/AetherEngineTests/Issue732PersistentHTTP1Tests.swift:177-177`

Suggested Code:

        let stream: [UInt8] = await withCheckedContinuation { continuation in
            DispatchQueue.global(qos: .utility).async {
                continuation.resume(returning: Self.drain(reader))
            }
        }

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. Test-only. This suite passed in the fork's runs of both test groups for this branch.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood—keeping this unchanged is reasonable for an upstream-sync PR. I won’t press for a fork-only change here.

The passing test runs don’t eliminate the potential cooperative-executor blocking, but this is a test-only concern better addressed upstream rather than as a requirement for this merge.

canHardwareDecode: true,
dvProfile: 7
)
#expect(decoder is DualLayerVideoDecoder)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug medium

False test failure occurs when the opted-in decoder test unconditionally requires DualLayerVideoDecoder despite selectVideoDecoder intentionally falling back to HardwareVideoDecoder if DoviFelComposer cannot initialize, including trait-enabled builds without a Metal device or command queue. Gate the composition-specific assertion on composer availability and test the fallback separately.

Prompt for LLM

File Tests/AetherEngineTests/SoftwareDecoderSelectionTests.swift:

Line 38:

False test failure occurs when the opted-in decoder test unconditionally requires DualLayerVideoDecoder despite selectVideoDecoder intentionally falling back to HardwareVideoDecoder if DoviFelComposer cannot initialize, including trait-enabled builds without a Metal device or command queue. Gate the composition-specific assertion on composer availability and test the fallback separately.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line comes unchanged from upstream 7.40.1; this PR only merges upstream, so it stays as upstream has it to keep the fork in step. Test-only, and it runs only with the DualLayerDolbyVision trait.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood—keeping this unchanged is reasonable for an upstream-sync PR. The potential false failure is limited to DualLayerDolbyVision-enabled tests where the composer cannot initialize; it does not affect default-trait builds or production behavior.

I’d treat this as a non-blocking upstream follow-up, not a required change for this merge.

@Quick104
Quick104 merged commit adf341b into main Oct 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XXXL 3000+ added + deleted lines (all files); change volume, not priority.

Projects

None yet

Development

Successfully merging this pull request may close these issues.