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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,20 @@ the public-API contract.

## [Unreleased]

_Nothing yet._
### Fixed

- **The H.264 composition-offset repair still left a periodically quantized constant-rate ladder in
decode order (AE#409 follow-up).** A physical tvOS source reports `PTS == DTS`, `video_delay=1`
and a `1/1200000` time base, but its exact `200202/5`-tick cadence is stored as the repeating
five-picture step cycle `40040,40041,40040,40040,40041`. The first repair required every decode
step to be identical, classified that ladder as nonuniform and repaired no packets. The demuxer
now derives an exact rational cadence and unique sampling phase from two repeated STTS periods;
declared and average frame rates only corroborate the observation and never construct timestamps,
and any approximate rate must remain within half a tick across the known full-stream span. A
complete dry run must safely place the sampled packets before repair is armed, while ambiguous,
non-repeating and genuinely variable timing remains untouched. Packet and container-index mapping
share the same rational lattice across IDRs and seeks, including unambiguous one-tick
resynchronization.

## [6.42.0] - 2026-08-25

Expand Down
12 changes: 10 additions & 2 deletions Sources/AetherEngine/Demuxer/Demuxer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1059,7 +1059,7 @@ public final class Demuxer: @unchecked Sendable {
func indexedKeyframes(streamIndex: Int32) -> [Int64] {
accessLock.lock()
defer { accessLock.unlock() }
let compositionOffset = compositionRepair?.decodeTimestampOffset
let activeCompositionRepair = compositionRepair?.isRepairing == true ? compositionRepair : nil
guard let ctx = formatContext,
streamIndex >= 0,
streamIndex < Int32(ctx.pointee.nb_streams),
Expand All @@ -1080,7 +1080,15 @@ public final class Demuxer: @unchecked Sendable {
// built from these IRAP positions matches the normalized packets (AE#105), then onto
// the repaired decode ladder if #409 moved it.
let folded = normalizedTimestamp(entry.pointee.timestamp, pos: entry.pointee.pos, timeBase: tb)
result.append(compositionOffset.map { folded &+ $0 } ?? folded)
if let activeCompositionRepair {
// An unplaceable index entry is safer omitted: mixing one raw timestamp into a
// repaired rational ladder can cut a segment one picture away from its keyframe.
if let repaired = activeCompositionRepair.repairedDecodeTimestamp(folded) {
result.append(repaired)
}
} else {
result.append(folded)
}
}
}
return result
Expand Down
Loading