/docs · SignalLab · Practical
Streaming clipping detection at scale
How to detect clipping in audio at ingest time without decoding the whole file.
If you run an audio platform — broadcasting, archiving, transcription — you ingest a lot of files. Many of them clip. Most upstream QA tools either don’t catch it or only catch it on full decode, which means you waste compute on every bad file before you know it’s bad.
There’s a simpler approach: detect clipping during the decode, in streaming fashion. Here’s the pattern that works.
The naive approach (and why it’s slow)
samples = decode(audio_file)
clipping_count = sum(1 for s in samples if abs(s) > 0.99)
This requires fully decoding the file into memory. For a multi-hour podcast, that’s a meaningful cost — and you only know whether the file is clipped after you’ve done the work.
Streaming detection
The streaming version processes blocks as they come out of the decoder:
def stream_clipping_check(decoder, threshold=0.99, run_length=3):
"""
Decode in blocks and stop early if hopeless.
Returns (total_clips, regions) or raises if catastrophic.
"""
total = 0
regions = []
current_run = 0
current_region_start = None
sample_index = 0
for block in decoder.blocks(): # generator yielding numpy arrays
for v in block:
if abs(v) > threshold:
if current_run == 0:
current_region_start = sample_index
current_run += 1
if current_run == run_length:
total += 1
else:
if current_run >= run_length:
regions.append((current_region_start, sample_index))
current_run = 0
current_region_start = None
sample_index += 1
# Early-exit if catastrophic
if total > 1000:
raise CatastrophicClippingError(f"{total} clip events, abandoning analysis")
return total, regions
Two things to notice:
- Run-length filtering: a single sample at -0.99 isn’t clipping — it’s a peak. Real clipping shows up as multiple consecutive samples flat at the ceiling. Requiring
run_length=3(three consecutive over-threshold samples) eliminates 90% of false positives. - Early-exit: if you find more than a few hundred clipping events, the file is broken. Bail and skip the rest. The downstream system can flag it and move on.
What “clipping” actually means
Strictly, clipping is when the analog signal exceeded what the ADC could represent and got truncated to the ceiling. In practice, in the digital domain:
- Hard clipping: consecutive samples at exactly ±1.0 (in float) or the integer maximum.
- Soft clipping: consecutive samples above ~0.95 with low slope. Usually an artifact of a compressor or limiter at the source.
- Intersample clipping: peaks that exceed 0 dBFS between samples after reconstruction. Invisible in sample-domain measurement; requires oversampling to detect.
A robust detector handles all three. For ingest-time QA, the first two are essential. The third is a “treat as warning, not error” case.
The thresholds that work
After running this on a few hundred thousand files in production:
- Threshold
0.99: matches “real” clipping at 16-bit and 24-bit with no false positives from float headroom. run_length=3: catches genuine clipping at typical sample rates (44.1k/48k) without missing real events.total > 1000: a catastrophe threshold for a typical short-form (sub-30-minute) file. Tune up for longer files.- Region merging: regions within ~50 samples (~1 ms at 48k) should be merged into one event. Otherwise a sustained clip gets reported as hundreds of micro-events.
Why this matters
A streaming clipping check costs you a few CPU cycles per sample, runs in lockstep with decode, and lets you reject bad files before any downstream tool touches them. At the scale of an audio archive, that’s the difference between a noticeable infra cost and an unnoticed one.
It’s also a good reminder that “do the analysis” and “do the cheap part of the analysis” are different choices. Many audio QA problems have a streaming-friendly cheap form.
Related
More in SignalLab docs