相比于音频信号处理中的其它算法,limiter 是一个存在感较低的算法,各种资料上所呈现出的原理和实现过程也相对简单,基本一看就懂。但是按照该原理所实现的 limiter 仍然需要接后处理才能满足实际需要。
本文首先介绍了 Matlab 上的实现,笔者称之为一阶递归平滑版本的 limiter,该版本也是上面所说的大多数资料上呈现的版本;然后介绍了 FFMPEG 里面的实现,笔者称之为逐采样点过渡平滑的 limiter。两个版本各有优缺点,可根据具体应用的需要选择相应的版本。
1. Limiter 的主要作用
在音频信号处理中,通常的做法是先将音频采样点归一化到 [−1.0,1.0] ,然后再对其施加各种各样的音频算法。然而在算法处理过程中可能会出现某些音频采样点的幅度超过1的情况。或者在将多路音频流混合成一路音频流的时候,采样点相加的过程中也有可能出现幅度超过1的情况。而使用 limiter 的主要目的就是在尽量不变动其它采样点的情况下,将这些采样点的幅度全部限制在1以内,以避免削波。
2. 简单粗暴做法

3. 简单粗暴做法的另一种理解:增益因子

4.一阶递归平滑版本的 Limiter

4.1 攻击时间和释放时间
关于攻击时间和释放时间的介绍可以参考 Matlab。下面笔者举例说明下对这两个时间的理解,后续 FFMPEG 实现的 limiter 也用到了这两个参数。
考虑一个正在参加短跑体测的大学生,他需要从 0m/s 快速加速到他的最大速度8m/s,等跑到终点后再慢慢减速到 0m/s。其中加速所用的时间在这里相当于攻击时间,减速所用的时间相当于释放时间。更一般的来说,攻击时间可以理解为从初始状态切换到期望状态所用的时间,释放时间可以理解为从期望状态恢复到初始状态所用的时间。在这里大学生的初始状态就是 0m/s,期望状态就是 8m/s。大部分情况下攻击时间短于释放时间。

4.2 存在的问题

DAFX: Digital Audio Effects Second Edition 中有提到为了较好地解决这个问题,可以后接一个 soft clipping。
笔者还想到另一个处理该问题的方法,但并不能保证 100% 解决。方法就是将 limiter 的阈值降低,也就是说不要用1或小于1但又特别特别接近于1的数来当阈值,而是用一个较小的阈值,比如 0.9 。
不过,经过上面的分析可以看到,这个版本的 limiter 可以做到零延迟,某些对延迟要求较高的应用,可以使用它。
5. 逐采样点过渡平滑版本的 Limiter
这儿讲的是 FFMPEG 中的实现,是笔者在偶然间发现的,但又不知该怎么称呼好,因此叫了这个名字。与上述版本相比,该版本的 limiter 使用不同的平滑方法。上面提到,一阶递归平滑版本存在增益因子滞后性的问题,从而导致处理完的采样点的幅度可能大于阈值,但它可以做到零延迟。而该版本可以做到增益因子零滞后,也就是能保证处理完的采样点的幅度不大于阈值,但它却做不到零延迟。也就是说该版本要计算当前采样点最终的增益因子,必须要用到未来采样点的信息。


上面只是简单说明了该版本的思想,看着也挺简单,没什么难度,但实际情况往往更加复杂。比如一段音频序列中短时间内出现多个幅度超出阈值的采样点,应该怎么处理才能保证让每个采样点都能满足要求那。这才是该版本的难点之一,笔者能力有限,不能用简洁的语言描述清楚这个过程。感兴趣的读者可以阅读FFMPEG 的实现,了解了其思想之后,看起来就能容易些。
6. 总结
在初步接到这个需求的时候,是想实现一个延迟可设的 limiter, 本以为不会花太多的时间和精力,应该很快就能完成。但随着深入学习研究,才发现一个小小的算法,要想一步一步优化它,让它更加完美,也并不是像它表面看起来那么简单。从发现问题到提出解决方案,这后面蕴含的思想是十分美妙的,这也是算法的魅力之一。
