Make the source's presentation window first-class: elst-aware reads, sync-frame trims, matching text-track edit list - #20
Open
cclements wants to merge 9 commits into
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l sync lookback Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… window Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With lookback dispatch (or an edit-list input whose window starts mid-media) the first dispatched frame precedes the first chapter's window. Opening the part there wrote the sync frame and then dropped the pre-window frames after it, leaving a hole in the part's bitstream. Deferring the open to the first frame overlapping the window makes the part's media the contiguous run from the preroll's sync frame — the same policy the single-file path uses. Also round PresentedDurationSamples instead of truncating, for sources whose mvhd and mdhd timescales differ. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is the follow-up we discussed in #19, covering both cases you raised plus the
text-track caveat.
What changes
1. Inputs with an edit list are read in presentation time (your case 2).
ElstBoxexposes the single-edit form we write (SingleEdit), andMpeg4FilegainsPresentationStartSample/PresentedDurationSamples/PresentedDuration. The chunkreaders now map their start/end bounds into each track's own media timeline via that
track's elst (
AddTrackdoes the mapping per-track, since an audio track with prerolland a text track present different media). Re-converting a split part no longer reads
the preroll as content, and
ConvertToMultiMp4aAsyncmaps chapter offsets the same way,so re-splitting a part is also correct.
2. Trimmed lossless output starts at a sync frame with an exact window (your case 1 —
the branding-strip path). The preroll queue from #19 is hoisted into a shared
SyncPrerollQueue, andConvertToMp4aAsyncwith user chapters now behaves exactly likea split part: output opens at the most recent sync frame at or before the trim start,
and a single elst presents exactly the requested window. For USAC the reader over-dispatches
a bounded 2 s lookback (sync truth only exists post-decrypt — same encryption constraint
as #18) and the filter selects the sync run. An untrimmed conversion is byte-identical
to 3.1.0 output (verified, see below) — no elst, no behavior change.
3. The chapter text track stays consistent with the trimmed audio (your caveat).
One nuance found while implementing: chapter samples are written on the presentation
timeline (first chapter at presentation zero), so an edit list literally matching the
audio one (same media_time) would double-shift the markers. What was actually broken is
that
SetDurationcopied the audio media duration (preroll included) onto the texttrack. The writer now gives the text track the presented durations and an identity edit
(media_time 0, same segment_duration), so both tracks present the same window. Verified
against Apple's own chapter API (details below) — happy to switch to a different mapping
if you had something else in mind.
Out of scope, as discussed: the decoder-side policy (decode from sync, discard PCM before
the window) belongs to AAXClean.Codecs — I'd like to do that as the companion PR next.
The public surface it needs (presentation properties, per-track dispatch mapping) is in
this PR.
Compatibility notes: source-compatible, but AAXClean.Codecs needs a rebuild against
this version —
IChunkReader.AddTrackgained an optional parameter and theMultipartFilterBaseconstructor gained an optionalmediaTimeOffset(defaulted, soexisting subclasses compile unchanged; the Codecs multipart filters should pass their
input's offset in the companion PR). Until the companion lands, Codecs conversions of
elst-bearing inputs behave as before for USAC (the 2 s lookback re-covers the preroll
the bounds mapping would have skipped) and slightly better for AAC-LC (the trailing
out-of-window tail is no longer read).
Mp4File.Durationdeliberately staysmedia-based to avoid a blast radius through Libation;
PresentedDurationis the newelst-aware property — happy to flip the default if you'd rather.
Verification (real Audible media, Apple decoder)
(13h08m) against v3.1.0 builds — outputs byte-identical except the 4 bytes of
mdhd creation/modification timestamps (UtcNow, differs every run).
identical sample count, identical stss, identical elst (segDur=35077051,
mediaTime=4738), identical audio payload MD5, Apple seek probes 5/5 incl. t=0.
Before this PR the round-trip dropped the window and the preroll became audible.
ConvertToMp4aAsyncwith a 2.5s–3600s chapter window:output opens on a sync run (elst mediaTime=28330), AVAsset duration exactly 3597.5 s,
seek probes 5/5, and decoded PCM at trim+10 s is bit-identical (max abs diff 0.0)
to source+12.5 s — the window offset is sample-exact.
round(2.5s·22050) mod 1024, the mid-frame remainder — probes 5/5. One measured,honest caveat: cross-correlation shows Apple's AudioFile layer ignores the elst for
AAC-LC (constant −853-sample lag, i.e. 38.7 ms early — same layer behavior we measured
in Write sync sample tables: derive stss for USAC (ISO/IEC 23003-3 §H.1), propagate source sync info — fixes seeking in Apple players #18, where even media_time+2112 changed nothing), while the AVAsset layer (Books,
QuickTime) honors it exactly (asset duration 3597.500). Pre-PR LC trims started on the
same frame with no declared window, so this is strictly better or equal at every
layer; container-side we can't do more than declare the window. USAC has no such
caveat: cross-correlation lag 0, PCM bit-identical.
durations sum to the parent's exactly (17538525 + 17538526 = 35077051), seek probes
pass, and PCM is bit-identical to the parent on both sides of the sub-boundary.
Testing this caught a real bug in an earlier revision of this branch: with early
dispatch, the multipart filter opened part 1 at the first dispatched frame and then
dropped the pre-window frames after it, leaving a hole in the part's bitstream — the
fix defers each part's open until the first frame overlapping its chapter window
(the same policy the single-file path uses), and the fresh-source split battery was
re-run afterward: outputs hash-identical to before the fix.
AVAsset.loadChapterMetadataGroupsreports thechapter at start 0.0 with duration 3597.5 — markers land on the presented timeline;
tkhd/mdhd/mvhd durations all consistent (structural check in the thread if you want it).
SyncPrerollQueuetests; theMultipartFilterBaserefactor is behavior-preserving and the split battery above ranthrough it).
🤖 Generated with Claude Code