mirror of https://github.com/jetkvm/kvm.git
Add ARM NEON soft-clipper to prevent clipping on sharp transients
Implements SIMD-optimized soft-clipping before Opus encoding to prevent digital clipping distortion on sharp transient attacks (e.g., plastic cup impacts, percussive sounds). Uses smooth saturation curve starting at ±30720 (~94% of max amplitude) to preserve audio quality while eliminating crackles and pops. Processes 8 samples per iteration using ARM NEON intrinsics for optimal performance on the ARM Cortex-A7 platform.
This commit is contained in:
parent
698978253b
commit
9e6ffb34e3
|
|
@ -206,6 +206,61 @@ static inline void simd_clear_samples_s16(short * __restrict__ buffer, uint32_t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Soft-clip audio samples to prevent digital clipping distortion
|
||||||
|
* Uses smooth saturation curve for transients that exceed ±30720 (~0.94 of max)
|
||||||
|
* Processes 8 samples per iteration using ARM NEON
|
||||||
|
*/
|
||||||
|
static inline void simd_soft_clip_s16(short * __restrict__ buffer, uint32_t samples) {
|
||||||
|
const int16_t threshold = 30720; // 0.9375 * 32768
|
||||||
|
const int16x8_t thresh_pos = vdupq_n_s16(threshold);
|
||||||
|
const int16x8_t thresh_neg = vdupq_n_s16(-threshold);
|
||||||
|
const int16x8_t max_val = vdupq_n_s16(32767);
|
||||||
|
const int16x8_t min_val = vdupq_n_s16(-32768);
|
||||||
|
|
||||||
|
uint32_t i = 0;
|
||||||
|
uint32_t simd_samples = samples & ~7U;
|
||||||
|
|
||||||
|
for (; i < simd_samples; i += 8) {
|
||||||
|
int16x8_t samples_vec = vld1q_s16(&buffer[i]);
|
||||||
|
|
||||||
|
// Detect samples exceeding positive threshold
|
||||||
|
uint16x8_t exceeds_pos = vcgtq_s16(samples_vec, thresh_pos);
|
||||||
|
// Detect samples below negative threshold
|
||||||
|
uint16x8_t exceeds_neg = vcltq_s16(samples_vec, thresh_neg);
|
||||||
|
|
||||||
|
// Apply soft saturation to samples exceeding thresholds
|
||||||
|
// For positive: scale down to range [threshold, max_val]
|
||||||
|
// For negative: scale up to range [min_val, -threshold]
|
||||||
|
int16x8_t clipped = samples_vec;
|
||||||
|
clipped = vbslq_s16(exceeds_pos,
|
||||||
|
vaddq_s16(thresh_pos, vshrq_n_s16(vsubq_s16(samples_vec, thresh_pos), 2)),
|
||||||
|
clipped);
|
||||||
|
clipped = vbslq_s16(exceeds_neg,
|
||||||
|
vaddq_s16(thresh_neg, vshrq_n_s16(vsubq_s16(samples_vec, thresh_neg), 2)),
|
||||||
|
clipped);
|
||||||
|
|
||||||
|
// Clamp to int16 range
|
||||||
|
clipped = vminq_s16(vmaxq_s16(clipped, min_val), max_val);
|
||||||
|
|
||||||
|
vst1q_s16(&buffer[i], clipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scalar: remaining samples
|
||||||
|
for (; i < samples; i++) {
|
||||||
|
int32_t sample = buffer[i];
|
||||||
|
if (sample > threshold) {
|
||||||
|
sample = threshold + ((sample - threshold) >> 2);
|
||||||
|
} else if (sample < -threshold) {
|
||||||
|
sample = -threshold + ((sample + threshold) >> 2);
|
||||||
|
}
|
||||||
|
// Clamp to int16 range
|
||||||
|
if (sample > 32767) sample = 32767;
|
||||||
|
if (sample < -32768) sample = -32768;
|
||||||
|
buffer[i] = (short)sample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// INITIALIZATION STATE TRACKING
|
// INITIALIZATION STATE TRACKING
|
||||||
|
|
||||||
static volatile sig_atomic_t capture_initializing = 0;
|
static volatile sig_atomic_t capture_initializing = 0;
|
||||||
|
|
@ -755,6 +810,9 @@ retry_read:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply soft-clipping to prevent digital clipping distortion on sharp transients
|
||||||
|
simd_soft_clip_s16(pcm_to_encode, opus_frame_size * capture_channels);
|
||||||
|
|
||||||
nb_bytes = opus_encode(enc, pcm_to_encode, opus_frame_size, out, max_packet_size);
|
nb_bytes = opus_encode(enc, pcm_to_encode, opus_frame_size, out, max_packet_size);
|
||||||
|
|
||||||
if (__builtin_expect(nb_bytes < 0, 0)) {
|
if (__builtin_expect(nb_bytes < 0, 0)) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue