Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/technical/building.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ bash tests/run_bd_audio_tests.sh # the sound a disc carries, written out
bash tests/run_vc1_tests.sh # the VC-1 encoder, put through a decoder 4
bash tests/run_vp9_av1_tests.sh # VP9 and AV1: cut, and still the source's bytes 5
bash tests/run_vfr_tests.sh # variable rate, both ways round 8
bash tests/run_source_clock_tests.sh # source clocks, range ends and VFR copying 80
bash tests/run_demux_tests.sh # what a reader loses by reading what it does not 9
bash tests/run_transrate_tests.sh # fitting a disc: the identity, the size, the cost 15
bash tests/run_join_tests.sh # several recordings into one file, and the seams 17
Expand Down
95 changes: 95 additions & 0 deletions docs/technical/source-clock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Source clocks and variable-rate range ends

The timestamp clock and the nominal picture rate serve different purposes.
The boundary encoder still uses the existing rate and quality settings. The
output timeline must also represent the source's display timestamps.

## When the clock changes

`Grid::of` refines a progressive, non-TS source only when it is variable-rate,
or its nominal frame period is not an integer number of source ticks (for
example, 23.976 fps stored in milliseconds). An ordinary 25 fps MP4, or a
23.976 fps MP4 on a 1/24000 clock, retains its existing grid and timescale.
An already suitable grid is returned unchanged, without fraction reduction.

`on_clock` chooses a subdivision that represents both the field grid and
the source tick. Only a newly aligned grid reduces its output time base.
Checked arithmetic and the existing 32-bit MP4 step/timescale limits bound
the result; if no such refinement fits, the existing bounded grid remains.
This does not promise exact ticks for every possible clock and file length.

The walk measures repeated holds against the typical positive display gap
as well as the average. A 40/40/80 ms pattern must not be called constant
merely because its average frame lasts about 53 ms. Millisecond-rounded
NTSC and a 2:3 cadence still do not count as variable by that test.

An unclassified progressive stream with B pictures and an average over 1%
below its declared rate needs its display times read: the public container
index contains decode timestamps. `ContainerIndex` declines that case,
so the default `auto` mode falls back to the packet scan. This can add a
sequential read for those sources; ordinary CFR keeps the fast table path.
An explicitly required container index reports the reason it cannot answer.
Seek-cache version 19 invalidates the former classification.

## A seam is not the end of a keep-range

At a seam inside one kept range, the following segment resumes on a measured
picture. Extending the preceding segment to a nominal frame length would
shift every picture after the seam. `SegmentCtx::last` distinguishes this
from the end of a keep-range.

At a range end, a normal picture retains the coded-length floor used before
this change. The review reproducer has a picture at 37.996 s and OUT at
38 s. Refining the clock must not turn that picture into a 4 ms flash; its
normal 23.976 frame lasts about 41.708 ms. The existing
`coded_length_unbounded` cases retain their time-based hold behavior,
including a range entirely inside a long held picture.

For newly refined CFR, the decoder's next measured picture compensates for
the planner's coarse-clock selection margin. A copy has that coverage from
the planner's terminating access point, including its leading pictures.

On a refined clock, the final displayed picture also stays in the write
queue until a later displayed picture arrives or the final hold is known.
Decoder reorder depth alone is insufficient: a copied H.264 P picture can
precede three B pictures while `has_b_frames` is two. Releasing that P
picture too early left a 40/40/80 ms selection about 27 ms short. The queue
uses the existing 256 MiB seam-buffer limit and reports an error if that
limit is exceeded. Other clocks retain the existing queue behavior.

## Reproducing the checks

Build the actual CLI with the project's FFmpeg 7.1 SDK, then run:

```sh
bash tests/run_source_clock_tests.sh
# Or select the executable and tools explicitly:
python3 tests/source_clock_tests.py --cut rust/target/release/smartcut \
--ffmpeg ffmpeg --ffprobe ffprobe --work-dir /tmp
bash tests/run_vfr_tests.sh
```

The 80-case suite generates AVC, HEVC and AV1 inputs, includes the reviewer's
unchanged 90-second 720p command, and checks single/multiple selections,
copy-only and single-picture cuts, long holds and CFR controls in MP4/MKV.
It checks decoded counts, all display PTS, decode ordering, movie and video
track ends, compressed copied pictures and their decoded pixels. AVC/HEVC
VCL bytes are checked separately from repeated parameter-set NAL units.
Matroska stores no DTS; equal inferred DTS on its millisecond clock are
recorded, while backward DTS are rejected. MP4 DTS must strictly increase.

Each run writes complete commands, hashes and results to a new directory.
`--fixtures <previous-run>/fixtures` reuses the exact input bytes. FFmpeg
7.1's synthetic B-frame VFR MP4 edit list can hide its last generated
picture: the reference pixel decoder alone uses `ignore_editlist=1` to
read all generated packets. Outputs always undergo normal demuxing and
strict decoding, with their end times checked independently.

This suite tests timing and copying, not AV1 sample-description compliance,
SAR propagation, M2TS arrival clocks, GUI playback or hardware players.

The review assertions use the original default indexing. Forcing `--index scan`
on that duplicate-PTS synthetic file retains a pre-existing unbounded-hold
case (796 pictures rather than 794, with an approximately 4 ms range bound)
in both v0.8.39 and this change. That separate selection/hold behavior is not
claimed fixed here.
Loading