← Back to Complete Guide

VFR to CFR in FFmpeg: Fix Audio Desync (2026 Guide)

Your clip starts perfectly in sync and ends four seconds off. The camera didn't write a broken file — it wrote a variable frame rate (VFR) file, and your editor assumes a constant frame rate (CFR). Premiere Pro, DaVinci Resolve, and After Effects all map audio against a fixed frame clock, so when the video's real timestamps drift, sync drifts with them.

By Hadi Karaki, maker of FormatifUpdated September 22, 202612 min read

TL;DR — the exact fix

One command, and the flags that actually matter

The fps filter converts to CFR. aresample=async=1 repairs the audio timestamps. -r alone does neither.

VFR to CFR — sync-safe conversion

ffmpeg -fflags +genpts -i input.mp4 -vf "fps=30000/1001,format=yuv420p" -af "aresample=async=1:first_pts=0" -c:v libx264 -crf 18 -preset slow -c:a aac -b:a 192k -ar 48000 -fps_mode cfr -movflags +faststart output.mp4
SettingValueWhy
Frame resamplefps=30000/1001Matches 29.97 exactly. Use 24, 25, 30, 60000/1001, or 60 to match your project
Audio repairaresample=async=1:first_pts=0Rebuilds audio timestamps against the new video clock
Audio rate-ar 48000Video timeline standard; avoids timeline resampling
Video quality-crf 18 -preset slowVisually near-lossless H.264
Mux mode-fps_mode cfrReplaces the deprecated -vsync cfr

What VFR and CFR actually mean

CFR writes a frame every fixed interval — at 30fps, exactly 33.333 ms apart, every time. VFR writes a frame only when the picture changes, then stamps it with the real capture time. A static shot might produce four frames in a second; motion produces thirty.

PropertyCFRVFR
Frame intervalConstant (33.333 ms at 30fps)Variable
File sizePredictableSmaller for static scenes
Editor compatibilityUniversalPoor — drift, stutter, frame mismatch
Typical sourcesCamcorders, OBS, ProRes/DNxHRPhone cameras, WebRTC, call recorders, many screen recorders
Audio sync over 40 minHoldsDrifts by seconds

The drift is arithmetic, not a bug. An editor conforming VFR to a fixed rate assumes every frame lasts 1/30 s. If the real average was 29.7 fps, a 40-minute clip accumulates roughly 24 seconds of phantom timeline — the audio, recorded on its own steady clock, slides out of position as you approach the end.

Step 1 — Confirm the file is actually VFR

Do not guess. Run this first:

Check reported frame rates

ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate,avg_frame_rate,time_base -of default=noprint_wrappers=1 input.mp4
  • r_frame_rate is the nominal rate the container advertises.
  • avg_frame_rate is the real average.

If the two values differ, the file is VFR. A file reporting r_frame_rate=30/1 and avg_frame_rate=2997/100 is variable.

When both look identical but you still get drift, check the raw frame timestamps:

Inspect frame timestamps

ffprobe -v error -select_streams v:0 -show_entries frame=pts_time -of csv=p=0 input.mp4

A CFR file shows a uniform step (0.000, 0.033, 0.067). Uneven gaps confirm VFR. On FFmpeg builds older than 5.x, replace frame=pts_time with frame=pkt_pts_time.

Step 2 — Convert to CFR at the right frame rate

Match the output rate to your project rate, not to what the phone claims:

Project timelineUse this fps value
23.976fps=24000/1001
24fps=24
25 (PAL)fps=25
29.97fps=30000/1001
30fps=30
59.94fps=60000/1001
60fps=60

A rounded rate is the most common self-inflicted desync: conforming a 29.97 clip to 30 introduces about one frame of error every 33 seconds — roughly 72 frames across a 40-minute recording.

Step 3 — Pick settings by source type

SourceTypical symptomCommand coreAudio fix
iPhone / Android cameraDrift in Premiere, breaks on conformfps=30000/1001aresample=async=1:first_pts=0
WebRTC / browser recordingChoppy playback, unstable durationfps=30-ar 48000 plus aresample
Meeting / call recorderDrift plus thin-sounding audiofps=30-ar 48000 (often not 48 kHz)
Screen / game captureHigh bitrate, uneven frame stepsfps=60-ar 48000
Phone slow motion (120/240 fps)Do not run the fps filter — keep the source ratec copy, conform in the NLEnot applicable

Delivery targets for the same job

TargetVideoQualityAudioContainer
Editing intermediateProRes 422 Proxy (-profile:v 0)n/a, intra-framepcm_s16le -ar 48000.mov
Editing (Avid / Windows)DNxHR LB (-profile:v dnxhr_lb)n/a, intra-framepcm_s16le -ar 48000.mov
Delivery / archiveH.264-crf 18 -preset slowaac -b:a 192k -ar 48000.mp4
Smaller deliveryH.265-crf 20 -preset mediumaac -b:a 192k -ar 48000.mp4

If the goal is also a smaller file after the sync fix, see how to make a video file smaller with FFmpeg.

Fixing audio that is already out of sync

Two different faults need two different tools. Diagnose before you touch anything.

Constant offset — early or late by the same amount everywhere

Delay audio by 0.5 s (audio was early)

ffmpeg -i input.mp4 -itsoffset 0.5 -i input.mp4 -map 0:v:0 -map 1:a:0 -c copy output.mp4

Advance audio by 0.5 s (audio was late)

ffmpeg -i input.mp4 -itsoffset -0.5 -i input.mp4 -map 0:v:0 -map 1:a:0 -c copy output.mp4

Progressive drift — fine at the start, worse by the end

The audio needs time-stretching, not shifting. Measure both streams:

Video duration

ffprobe -v error -select_streams v:0 -show_entries stream=duration -of csv=p=0 input.mp4

Audio duration

ffprobe -v error -select_streams a:0 -show_entries stream=duration -of csv=p=0 input.mp4

Then compute atempo = audio_duration ÷ video_duration. If video is 3600.000 s and audio is 3599.020 s:

Stretch audio to match the video clock

ffmpeg -i input.mkv -c:v copy -af "atempo=0.999728" -c:a aac -b:a 192k -ar 48000 output.mp4

atempo accepts 0.5 to 2.0 per instance; chain filters (atempo=0.8,atempo=0.8) for anything beyond that. If timestamps are simply wrong while the audio data is intact, let aresample repair them instead:

Repair timestamps without stretching

ffmpeg -i input.mkv -c:v copy -c:a aac -b:a 192k -ar 48000 -af "aresample=async=1:first_pts=0" output.mp4

Why -r 30 alone does not fix desync

Three flags look similar and behave completely differently. This is where most guides send you wrong.

FlagPositionWhat it actually doesEffect on sync
-r 30Before -iReinterprets the source as 30fps, changing durationBreaks sync, can cause slow motion
-r 30After -iDrops or duplicates frames to reach 30fpsCan leave audio timestamps untouched
-vf "fps=30"FilterResamples every frame timestamp to a true 30fps clockFixes the video clock
-fps_mode cfrOutput optionWrites constant-rate timestamps, replaces -vsync cfrBelt-and-braces alongside fps

The fps filter is the correct tool because it rewrites the timeline, not just the frame count. It duplicates frames where the source was slow and drops them where it was fast, so total duration is preserved and the audio stays anchored.

  • Never use -c copy to fix VFR. Stream copy preserves the original timestamps exactly, so the drift survives the remux untouched.
  • -vsync is deprecated. It was superseded by -fps_mode in FFmpeg 5.1. Older builds may still accept it with a warning; new scripts should use -fps_mode.

For editors: build a CFR intermediate instead

If the footage is going into an NLE, a CFR delivery encode is the wrong output. Transcode once to an intra-frame intermediate — it decodes instantly, so it also removes the playback lag that 4K H.264 and HEVC cause.

1080p ProRes Proxy with PCM audio — the standard editing intermediate

ffmpeg -i input.mp4 -c:v prores_ks -profile:v 0 -pix_fmt yuv422p10le -vf "fps=30000/1001,scale=1920:-2:flags=lanczos" -c:a pcm_s16le -ar 48000 -fps_mode cfr -movflags +faststart output.mov
CodecProfile flagData rate (1080p29.97)Approx. size / hour
ProRes Proxy-profile:v 045 Mbps~20 GB
ProRes LT-profile:v 1102 Mbps~45 GB
ProRes 422-profile:v 2147 Mbps~66 GB
ProRes 422 HQ-profile:v 3220 Mbps~99 GB
DNxHR LB-profile:v dnxhr_lb~36 Mbps~16 GB

For a full breakdown of when each codec wins, see ProRes vs DNxHR compared.

Premiere Pro users

The Modify → Interpret Footage → Conform to Frame Rate option is non-destructive but only re-times existing frames. It trades drift for judder and does not repair variable timing. Transcode instead.

Batch-convert a folder of VFR clips

macOS and Linux, using bash

convert-vfr-folder.sh

#!/usr/bin/env bash
set -euo pipefail

SRC="input"
OUT="cfr"
FPS="30000/1001"
mkdir -p "$OUT"

while IFS= read -r f; do
  base=$(basename "$f")
  base=${base%.*}
  out="$OUT/$base.mp4"
  echo "Converting: $out"
  ffmpeg -hide_banner -nostdin -y -fflags +genpts -i "$f" -vf "fps=$FPS,format=yuv420p" -af "aresample=async=1:first_pts=0" -c:v libx264 -crf 18 -preset slow -c:a aac -b:a 192k -ar 48000 -fps_mode cfr -movflags +faststart "$out"
done < <(find "$SRC" -type f -iname '*.mp4' -o -type f -iname '*.mov' -o -type f -iname '*.mkv')

Windows, using PowerShell

convert-vfr-folder.ps1

$Src = "input"; $Out = "cfr"; $Fps = "30000/1001"
New-Item -ItemType Directory -Force -Path $Out | Out-Null

Get-ChildItem -Path $Src -Recurse -File -Include *.mp4,*.mov,*.mkv | ForEach-Object {
  $rel = $_.FullName.Substring((Resolve-Path $Src).Path.Length + 1)
  $outFile = Join-Path $Out ([System.IO.Path]::ChangeExtension($rel, ".mp4"))
  New-Item -ItemType Directory -Force -Path (Split-Path $outFile -Parent) | Out-Null
  Write-Host "Converting: $outFile"
  $ffArgs = @('-hide_banner','-nostdin','-y','-fflags','+genpts','-i',$_.FullName,
    '-vf',"fps=$Fps,format=yuv420p",'-af','aresample=async=1:first_pts=0',
    '-c:v','libx264','-crf','18','-preset','slow',
    '-c:a','aac','-b:a','192k','-ar','48000',
    '-fps_mode','cfr','-movflags','+faststart',$outFile)
  & ffmpeg @ffArgs
}

If the whole folder is heading to a mixing stage rather than an editor, batch audio loudness normalisation belongs in the same pass.

Verify the fix

Confirm constant frame rate

ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate,avg_frame_rate -of default=noprint_wrappers=1 output.mp4

r_frame_rate and avg_frame_rate must now be identical. If they are, the timeline clock is constant and the drift is gone.

Troubleshooting

SymptomCauseFix
Audio still drifts after -r 30Output -r left timestamps variableUse -vf "fps=30" plus -fps_mode cfr
Sync is fine in VLC, broken in PremiereThe NLE conforms VFR differently from the playerTranscode to CFR before import
Output plays at half speed-r placed before -i reinterprets the sourceMove rate control into the filter
Duration changed after conversionRate mismatch, 29.97 forced to 30Use the exact fractional rate
Unknown option fps_modeFFmpeg older than 5.1Upgrade, or use -vsync cfr on legacy builds
Audio still off by a fixed amountConstant offset, not driftApply -itsoffset
-c:a copy fails with -afStream copy cannot be filteredRe-encode audio as AAC
genpts warnings, broken timestampsMissing PTS in the sourceAdd -fflags +genpts before -i

Do it offline across a whole folder

If the folder holds hundreds of clips, the terminal loop above is the honest free answer. Formatif Pro ($8.95 one-time) does the same CFR conversion offline on Windows and Linux — point it at the folder, set the frame rate and codec once, and it processes every file with per-file status instead of a silent failed loop. It handles the audio resample in the same pass, so sync survives. You can download the free trial and compare on your own footage.

VFR and CFR questions, answered

What is the difference between VFR and CFR?

CFR writes every frame at a fixed interval, so the timeline is predictable. VFR writes frames only when the picture changes and stamps each with its real capture time. Editors assume CFR, which is why VFR files drift.

Why is my iPhone video only out of sync in Premiere Pro?

Phones record VFR. Players like VLC ignore the frame clock and play timestamps as they arrive, so they stay in sync. Premiere Pro conforms the video to the sequence rate and maps audio against that fixed clock, which exposes the accumulated timing error.

Does converting VFR to CFR reduce quality?

The fps filter itself is lossless — it duplicates and drops frames, it does not interpolate or alter pixels. The only quality loss comes from the encoder you choose. At CRF 18 with the slow preset it is not visible; a ProRes intermediate is effectively transparent.

Why doesn't -r 30 fix the desync?

As an output option it adjusts frame count without rebuilding the timeline clock, and as an input option it reinterprets the source and changes duration. The fps filter is what actually resamples timestamps.

Is -vsync still valid?

It is deprecated. FFmpeg 5.1 introduced -fps_mode (cfr, vfr, passthrough, auto) as the replacement. Legacy builds accept -vsync cfr with a warning; new scripts should use -fps_mode cfr.

Which frame rate should I convert to?

Always your project rate. A 29.97 source in a 29.97 timeline is perfect; forcing it to 30 creates about one frame of error every 33 seconds.

Can I fix VFR without re-encoding?

No. Stream copy (-c copy) preserves the original timestamps, which is exactly what is broken. VFR to CFR requires resampling, so at least the video must be re-encoded.

Keep learning

Stop fighting frame rates in the terminal.

Formatif Pro converts VFR footage to a constant frame rate across whole folders, offline, with the audio repaired in the same pass. Formatif Pro $8.95, Formatif AI $12.95, or the bundle at $16.95. All one-time purchases with a 3-day free trial.

Fully offline, no uploads3-day free trialPer-file conversion statusThe FFmpeg commands above stay free forever
HK

Written by

Hadi KarakiLinkedIn

Founder of Formatif, a local-first offline media processing suite for Windows and Linux. Hadi writes about video, image, and audio formats, compression, and privacy-first desktop workflows.