Constant frame rate (CFR) means successive video pictures are scheduled at regular presentation-time intervals. Variable frame rate (VFR) means those intervals change. Two files can have the same average spacing—and look like the same “FPS” at a glance—while holding individual pictures on screen for very different amounts of time.
That distinction matters when you need a dependable frame-by-frame timeline, are investigating an editing issue, or want to know what a frame-rate checker can actually establish. It does not mean VFR is inherently damaged or CFR is always the better recording format.
CFR vs. VFR at a glance
| Question | Constant frame rate | Variable frame rate |
|---|---|---|
| When does the next picture appear? | At a regular interval, within timestamp precision | After an interval that may differ from the previous one |
| Can the video have an average FPS value? | Yes | Yes; an average can describe the recording without describing its pacing |
| Can two adjacent pictures look identical? | Yes | Yes |
| Does a fractional FPS value identify the timing mode? | No | No |
| Is conversion required before editing? | Not on account of timing mode alone | Only if the intended application or workflow needs it, or a demonstrated problem justifies it |
“Frame rate” can describe a nominal setting, a measured average, or the instantaneous spacing of pictures. None of those is interchangeable with the others. The most useful evidence for CFR versus VFR is the presentation schedule itself.
The same average spacing can hide two different timelines
Consider three original, video-only test clips made with FFmpeg. Each contains 20 decoded pictures with first and last presentation timestamps of 0 ms and 1,900 ms. The clips use a 1/1,000-second time base, so the timestamps below are exact millisecond ticks in these fixtures.
| Clip | Gaps between successive picture timestamps | Mean of the 19 gaps | Equivalent rate over the first-to-last timestamp span |
|---|---|---|---|
| CFR control | 100 ms × 19 | 100 ms | 10 intervals/s |
| Uneven VFR | 50 ms × 9; 150 ms × 9; 100 ms × 1 | 100 ms | 10 intervals/s |
| VFR that starts late | 100 ms × 13; 50 ms × 3; 150 ms × 3 | 100 ms | 10 intervals/s |
The mean is a calculation from the decoded presentation intervals, not an FFprobe avg_frame_rate reading. In every case, 19 timestamp gaps add up to 1,900 ms; therefore, 19 ÷ 1.9 = 10 gaps per second. This comparison deliberately excludes the last picture's display duration, which is not established by subtracting successive timestamps. Do not substitute this span-based figure for a container's full-playback FPS calculation.
Here is a small part of the actual timestamp manifest. Picture indices start at zero; timestamps are measured in milliseconds.
| Picture index | CFR PTS | Uneven VFR PTS | Late-onset VFR PTS |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 1 | 100 | 50 | 100 |
| 2 | 200 | 200 | 200 |
| 3 | 300 | 250 | 300 |
| 4 | 400 | 400 | 400 |
| 12 | 1,200 | 1,200 | 1,200 |
| 13 | 1,300 | 1,250 | 1,250 |
| 14 | 1,400 | 1,400 | 1,400 |
| 15 | 1,500 | 1,450 | 1,450 |
| 18 | 1,800 | 1,800 | 1,800 |
| 19 | 1,900 | 1,900 | 1,900 |
The uneven clip sometimes advances to its next picture after just 50 ms, then waits 150 ms for the following one. The CFR clip advances every 100 ms. Yet the overall first-to-last span and mean gap are identical. The late-onset clip is more deceptive: its first 12 gaps are a perfectly regular 100 ms before different intervals appear. Inspecting only the opening second would miss the change.
Reproducibility note: These are observed decoded timestamps from synthetic FFV1-in-Matroska files generated and probed with FFmpeg/FFprobe 7.1.5. The clips have no audio. The supplied 7.1.5 fixtures were inspected and the interval schedules were regenerated on 6.1.1 with an explicit encoder time base. This is controlled synthetic video, not an uploaded user recording.
What an FPS number—and a short sample—can tell you
There are three useful levels of evidence. They answer different questions:
| Check | What it gives you | What it cannot establish by itself |
|---|---|---|
| Stream metadata | Reported rates such as r_frame_rate and avg_frame_rate, plus the stream's time_base | That every presentation interval is uniform |
| Sampled estimate | A quick picture of cadence within the portion actually inspected | The timing mode of a part of the file the sample never reached |
| Whole-file timestamp inspection | A list of decoded presentation times and intervals across the accessible stream | That timestamps are error-free, that playback looks smooth, or that a particular editor will maintain audio sync |
This is why two apparently conflicting readouts do not necessarily mean a tool is broken. In the supplied Matroska fixtures generated on 7.1.5, FFprobe reports r_frame_rate=10/1 and avg_frame_rate=10/1 for CFR, but r_frame_rate=20/1 and avg_frame_rate=0/0 for both VFR clips. The latter does not mean the picture sequence has “zero FPS.” Those fields are not interchangeable with measured presentation intervals, and their interpretation can depend on the container and demuxer. Regenerating the same interval schedules with FFmpeg 6.1.1 and an explicit 1/1000 encoder time base produced 10/1 in both rate fields even for the uneven clips. A difference between those fields is therefore not a universal VFR detector, and equality does not prove CFR either.
For a quick preliminary read, our Video FPS Checker can provide metadata and a sampled FPS estimate. It does not perform the definitive full-file CFR/VFR audit described below. If you need to examine nearby visible pictures, our Frame-by-Frame Video Viewer is an inspection aid, subject to browser decoding support—not a complete timing analysis.
An important distinction: a video can repeat the same image at regular timestamps and still be CFR. Likewise, a steady fractional rate—for example, 24000/1001 pictures per second with uniform intervals of 1001/24000 seconds—is CFR. Neither repeated content nor a decimal-looking FPS number identifies VFR.
How to inspect presentation intervals with FFprobe
The following workflow examines decoded video-frame timestamps. It is more informative than reading a single rate field. You will need FFmpeg and FFprobe; the commands assume they are on your command path.
1. Read the metadata without deciding yet
ffprobe -v error -select_streams v:0 -show_entries "stream=codec_name,time_base,r_frame_rate,avg_frame_rate,nb_frames,duration:format=duration" -of json "input.mp4"v:0 selects the first video stream. Record time_base before interpreting integer timestamps. Treat nb_frames as a reported field, not a guarantee of a full decoded count. The two FPS fields are useful context, not a final classification.
2. Export the decoded presentation timestamps for the whole file
ffprobe -v error -select_streams v:0 -show_streams -show_frames -show_entries "stream=time_base,r_frame_rate,avg_frame_rate:frame=best_effort_timestamp" -of json "input.mp4" > timing.jsonThis command has no -read_intervals option, so FFprobe attempts to enumerate all decodable frames in the selected video stream. It can take time on long files. It writes a JSON report; it does not rewrite the input. The best_effort_timestamp field is a decoder-derived presentation timestamp in the stream's time-base units. It is more appropriate for displayed picture spacing than interpreting decode timestamps (DTS) as display times.
Save the following as inspect_timing.py alongside timing.json, then run python3 inspect_timing.py (or python inspect_timing.py on a system where that invokes Python 3):
import json
from collections import Counter
from fractions import Fraction
with open("timing.json", encoding="utf-8") as f:
report = json.load(f)
unit = Fraction(report["streams"][0]["time_base"])
frames = report["frames"]
pts = [f.get("best_effort_timestamp") for f in frames]
if len(pts) < 2 or any(p in (None, "N/A") for p in pts):
raise SystemExit("Missing or insufficient timestamps: review manually")
# Integer ticks × rational time base avoids float-rounding comparisons.
gaps = [(int(b) - int(a)) * unit for a, b in zip(pts, pts[1:])]
print("Decoded frames:", len(pts), "Timestamp intervals:", len(gaps))
for gap, count in sorted(Counter(gaps).items()):
print(f"{float(gap * 1000):.3f} ms: {count} intervals")
if any(g <= 0 for g in gaps):
print("Review: at least one non-positive presentation interval")For our three fixtures, that interval count is 19, not 20: the first picture has no preceding picture from which to compute a gap. A distribution containing materially different intervals is evidence of nonuniform timing. If you need to find where the timing changes, keep the per-frame timestamps or generate an indexed CSV rather than retaining only the summary counts. The editorial package contains an extended script that outputs that CSV.
3. Use vfrdet as a second check, not as the sole verdict
ffmpeg -hide_banner -i "input.mp4" -vf vfrdet -an -f null -Look near the end of the output for VFR:. Our full-file tests reported VFR:0.000000 (0/19) for the CFR control, VFR:0.947368 (18/1) for the uneven sample, and VFR:0.368421 (7/12) for the late-onset sample. These are observed filter diagnostics, not percentages of the video that will stutter or percentages of “bad frames.” The filter compares frame-to-frame timing under its own method; it does not diagnose audio synchronization or tell you how an editor will handle the file. SWGDE's technical note likewise recommends using frame information to investigate actual time variance.
4. Account for timestamp precision and unusual files
Avoid treating every tiny numerical difference as proof of meaningful VFR. Timestamps are stored in discrete time-base ticks. For example, a mathematically uniform 1/24-second interval is 41⅔ ms; a timeline stored to whole milliseconds may alternate between 41 ms and 42 ms gaps to approximate it. That is a calculation illustrating quantization—not a test result from our fixtures.
Compare deviations with the time base and the intended cadence. Keep exact integer ticks for analysis, note missing or repeated timestamps, and inspect discontinuities separately. Rounded six-decimal seconds are useful for display but do not prove exact equality of rational timestamps. Also remember that two successive picture starts do not, on their own, establish the duration of the final picture.
Why audio trouble does not automatically mean VFR
A/V symptoms tell you where to investigate, not their cause. Compare the original file with the result in your actual target application and check identifiable audio/video events near both the beginning and the end.
| What you observe | More useful next check |
|---|---|
| Speech is about 200 ms late at both the beginning and the end | Look for a fixed start offset, track alignment, edit or export shift |
| Speech lines up early but becomes progressively late | Compare audio/video clocking, timestamp handling, retiming, decoding and export behavior; inspect the source's video intervals |
| Sync jumps after a particular segment | Inspect that segment for timestamp discontinuities, dropped material, an edit boundary or a damaged recording |
| Playback looks uneven but sound remains synchronized | Inspect presentation gaps and device/player performance before converting anything |
The constant-offset and increasing-drift cases are different problems. Neither proves VFR. A recording can have variable intervals and play correctly when its timestamps are respected; conversely, a nominally CFR source can suffer sync problems for unrelated reasons.
Editor support is also version- and workflow-specific. Adobe's Premiere Pro VFR FAQ documents variable-frame-rate handling and circumstances in which transcoding can be preferable, notably some proxy or consolidation workflows. Treat that documentation as guidance, not proof that your current editor version will fail with every VFR clip.
When conversion to CFR is worth doing
Preserve the original timing when the file plays and edits correctly. Consider a CFR copy when your specific destination requires regular-frame timing, a repeatable edit/export problem has been demonstrated, or a downstream process assumes an evenly spaced frame grid. Keep the source file unchanged so you can compare results.
HandBrake: In its frame-rate documentation, HandBrake distinguishes Variable, Constant and Peak Frame Rate modes. Use Constant Frame Rate when a CFR deliverable is required; choose the intended output FPS rather than assuming “Same as Source” by itself guarantees unchanged timing in every mode. Check the preset and the output—not only the label before encoding. HandBrake notes that CFR conversion is most appropriate for specific compatibility situations, rather than every source automatically.
FFmpeg: This documentation-based example creates a new 30 fps CFR H.264/AAC MP4. The audio path is not part of the video-only fixture test:
ffmpeg -n -i "input.mp4" -vf "fps=30" -fps_mode cfr -c:v libx264 -preset medium -crf 18 -c:a aac -b:a 192k "output-cfr30.mp4"The fps filter creates an evenly timed output by selecting, dropping or repeating pictures as needed; -fps_mode cfr requests constant-rate video synchronization. You are making a new presentation schedule, not recovering motion that the camera failed to capture. The example uses a re-encode, so expect different file size and potentially different picture count, quality or audio processing. Check whether the source contains audio and whether its timing, duration and channel configuration survive as intended. This general H.264/AAC recipe is documentation-based; the audio path has not been independently tested in the synthetic experiment below.
In a separate controlled video-only test, we converted the 20-frame uneven FFV1 source to 10 fps with:
ffmpeg -i "vfr10.mkv" -vf "fps=10" -fps_mode cfr -c:v ffv1 "vfr10_converted_cfr10.mkv"The resulting decoded PTS gaps were 100 ms × 19, and the output still held 20 pictures. Pixel-frame hashes matched the original pictures in the same order. That is one observed result, not a promise that other VFR-to-CFR conversions will preserve every frame; different source intervals, target rates or end-of-stream handling can change which pictures are selected or repeated.
Verify the output, not just the export setting
After any conversion, run the same FFprobe metadata, full timestamp inspection and (optionally) vfrdet commands on the new output. Then compare its decoded picture count and start/end presentation times against the original, and inspect fast movement for repeated or missing motion. If the file has audio, check synchronized events at the beginning, middle and end in the application where it will actually be used.
For frame extraction, the practical consequence is straightforward: a frame index identifies a picture in decoded sequence order, while a presentation timestamp identifies when that picture should appear. On a VFR source, converting “frame number ÷ average FPS” into a source timestamp can select the wrong moment. Use the source's real timestamps when time alignment matters; the FFmpeg selection guide compares those selection policies.
An FPS average is useful context, not a map of picture spacing. Read metadata for orientation, sample for a quick estimate, and inspect whole-file presentation intervals when the distinction changes your next decision.
A decimal-looking rate alone is not variability: see 23.976 versus 24 FPS and 29.97 versus 30 FPS for the constant fractional-rate comparisons.
Try the original examples
Timing fixtures, CSV manifests and generation scripts ↓Synthetic examples for inspection and learning. Keep your original media unchanged.
Owned 20-picture CFR/VFR fixtures and late-onset variability were inspected and regenerated on FFmpeg 6.1.1 with an explicit encoder time base. Audio-sync diagnosis remains workflow guidance, not a tested repair.
Examples and calculations are scoped to their stated settings. Source links support the instructions; an illustration is not a screenshot of an application test.