How to Prevent Audio Pops at Cut Boundaries in Video Editing

Audio pops occur at cut boundaries when PCM samples jump abruptly between non-zero values without zero-crossing, and you eliminate them by applying short 30ms fade-ins and fade-outs to every video segment using FFmpeg's afade filter.

When concatenating video segments during post-processing, abrupt discontinuities in the audio waveform create sharp edges that the human ear perceives as clicks or pops. The browser-use/video-use repository solves this problem by implementing automated micro-fades during the extraction phase, ensuring smooth audio transitions without perceptible volume drops.

What Causes Audio Pops at Cut Boundaries?

Audio pops arise from waveform discontinuities at the exact frame where one clip ends and another begins. Pulse-code modulation (PCM) samples at cut points are rarely at zero-crossing; instead, the waveform jumps instantaneously from one amplitude value to another. This sharp transition contains high-frequency energy that manifests as an audible click.

The severity depends on the amplitude difference between the final sample of the first clip and the initial sample of the second clip. Without processing, these artifacts persist through final encoding and delivery.

The 30ms Fade Strategy in video-use

The repository prevents audio pops at cut boundaries by baking 30-millisecond fades into every extracted segment. This duration is long enough to smooth discontinuities yet short enough to remain imperceptible as a deliberate fade effect.

According to the source code in helpers/render.py (lines 149-190), the implementation constructs an FFmpeg audio filter that applies fade-in at the start and fade-out at the end of each segment.

Calculating Fade Parameters

The fade logic handles edge cases where segments might be shorter than the fade duration:


# helpers/render.py – fade calculation (Rule 3)

fade_out_start = max(0.0, duration - 0.03)
af = f"afade=t=in:st=0:d=0.03,afade=t=out:st={fade_out_start:.3f}:d=0.03"

The max(0.0, duration - 0.03) calculation ensures the fade-out begins 30ms before the clip ends, but never starts before time zero, preventing filter errors on extremely short segments.

Applying Filters During Segment Extraction

The extract_segment function (lines 190-207 in helpers/render.py) passes the constructed filter to FFmpeg via the -af option:

cmd = [
    "ffmpeg", "-y",
    "-ss", f"{seg_start:.3f}",
    "-i", str(source),
    "-t", f"{duration:.3f}",
    "-vf", vf,
    "-af", af,               # ← 30ms fades applied here

    "-c:v", "libx264", "-preset", preset, "-crf", crf,
    "-pix_fmt", "yuv420p", "-r", "24",
    "-c:a", "aac", "-b:a", "192k", "-ar", "48000",
    "-movflags", "+faststart",
    str(out_path),
]

Because each segment ends with a fade-out to silence and the subsequent segment begins with a fade-in from silence, the concatenated result contains continuous waveform transitions.

Implementation Examples

Raw FFmpeg Command

To apply this technique manually without the Python wrapper:

ffmpeg -i input.mp4 -ss 12.345 -t 3.210 \
       -af "afade=t=in:st=0:d=0.03,afade=t=out:st=3.177:d=0.03" \
       -c:v copy -c:a aac -b:a 192k output_clip.mp4

Note that st=3.177 equals 3.210 - 0.033, accounting for the 30ms fade duration.

Python Integration

Using the repository's extract_segment function automatically applies the fade filters:

from pathlib import Path
from helpers.render import extract_segment

source = Path("interview_footage.mp4")
start_time = 45.5
duration = 8.0

extract_segment(
    source=source,
    seg_start=start_time,
    duration=duration,
    grade_filter="",           # no color grading

    out_path=Path("segment_with_fades.mp4")
)

This processes the clip with fades baked into the output, ready for seamless concatenation.

Handling Edge Cases

The implementation guards against three specific failure modes:

  • Segments shorter than 30ms: The max() function clamps the fade-out start to zero, preventing negative timestamps in the FFmpeg filtergraph.
  • Inconsistent segment lengths: Every segment processed through extract_segment receives identical fade treatment, ensuring uniform audio levels across the timeline.
  • Codec compatibility: Because afade operates on decoded PCM samples before re-encoding, the technique works with AAC, MP3, FLAC, or any FFmpeg-supported audio codec.

Summary

  • Audio pops at cut boundaries result from abrupt PCM sample jumps when waveforms do not cross zero between segments.
  • The video-use repository eliminates these artifacts by applying 30ms fade-ins and fade-outs to every extracted segment.
  • The helpers/render.py module automatically calculates fade parameters to prevent exceeding segment durations.
  • FFmpeg's afade filter processes transitions during extraction, baking smooth audio directly into output files.

Frequently Asked Questions

Why 30 milliseconds specifically for audio fades?

This duration balances artifact elimination with perceptual transparency. Thirty milliseconds is long enough to smooth the discontinuity that causes audio pops at cut boundaries, yet short enough that listeners perceive the audio as cutting cleanly rather than fading audibly.

Can this method cause gaps or overlaps when concatenating segments?

No. Each segment ends with a fade-out to digital silence (zero amplitude) and begins with a fade-in from silence. When concatenated, the transition moves from zero to zero, creating a continuous waveform without gaps or overlaps between clips.

What happens if a video segment is shorter than 30ms?

The code prevents filter errors by calculating fade_out_start = max(0.0, duration - 0.03). For segments shorter than 30ms, this clamps the fade-out start time to zero, causing FFmpeg to shorten the fade proportionally rather than throwing an error or requesting negative timestamps.

Does this approach work with lossless audio codecs like FLAC?

Yes. The afade filter operates on raw PCM samples before the final encode stage, making it codec-agnostic. Whether encoding to AAC at 192kbps or FLAC for archive purposes, the fade processing occurs at the sample level, ensuring pops are eliminated regardless of the output format.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →