Fixing Audio/Video Sync in User-Generated Clips
FFmpeg's input seeking hits the nearest keyframe for video but the exact timestamp for audio -- causing A/V desync in clips from commission meeting recordings.
Users creating video clips from Democracy.cc meeting recordings were getting audio that didn't match the video -- lips moving out of sync, sometimes by several seconds. The clips started at slightly wrong points and the audio drifted from there.
The Root Cause
FFmpeg's -ss flag before the input file (input seeking) seeks to the nearest keyframe for video but the exact timestamp for audio. In modern video compression, keyframes appear every 2-10 seconds. If you request timestamp 26,708 but the nearest keyframe is at 26,705, video starts 3 seconds early while audio starts exactly on time.
# BEFORE: input seeking -- video snaps to keyframe, audio doesn't
ffmpeg -ss 26708 -i meeting.mp4 -t 3765 -c copy output.mp4
The Fix
Two-pass seeking: seek approximately with input seeking (fast, skips to nearest keyframe), then trim precisely with output seeking:
# AFTER: two-pass seeking with timestamp normalization
ffmpeg -ss 26708 -i meeting.mp4 -ss 0 -t 3765 \
-avoid_negative_ts make_zero \
-fflags +genpts \
-c:v libx264 -c:a aac output.mp4
The key flags: -avoid_negative_ts make_zero shifts negative timestamps to zero (prevents players from showing blank frames at the start), and -fflags +genpts regenerates presentation timestamps for a clean timeline. Re-encoding (libx264/aac) instead of -c copy ensures frame-accurate cuts rather than keyframe-aligned ones.
Results
| Metric | Before | After |
|---|---|---|
| A/V sync accuracy | 2-10s drift | Frame-accurate |
| Clip start precision | Nearest keyframe | Exact timestamp |
| Processing speed | Fast (copy) | Slower (re-encode) |
| File size | Same as source | Slightly smaller |
The re-encode tradeoff is worth it -- clips are typically 1-60 minutes from multi-hour meetings, so processing time is modest, and the output quality is correct.
Built while working on Democracy.cc.
Back to danielships ->