游戏里的 UI 确认音、提示音或者视频转场音效,如果直接在波形起始位置播放,很容易听到一声短促的咔哒声。这个问题并不总是录音素材本身有缺陷,更多时候是播放逻辑没有对电平做平滑处理。数字音频是按采样点还原声波的,当第一个采样点的幅值从静音直接跳到较大数值,扬声器振膜会被要求瞬间移动到一个比较远的位置,这种不连续会表现为高频爆音或听感上的生硬。要解决它,核心思路是在声音的起止阶段加入增益过渡。

增益过渡听起来像是一个简单的小技巧,但它在实际项目中的影响范围很广。从短促的点击音到持续的环境音,只要缺少淡入淡出,播放和停止瞬间都可能产生不自然的瞬态。尤其是移动端扬声器、蓝牙耳机这类动态余量较小的设备,增益突变还容易触发保护电路或产生可闻的失真。下面先拆解突兀感的来源,再逐步给出淡入淡出和音量包络的实现方式。
音效突兀的成因:瞬态不连续与增益跳变
数字音频播放的本质,是把 PCM 采样点按固定频率送给数模转换器。假如一个音效文件的第一帧幅值是 0.35,而播放前系统处于静音状态,输出电平就会在极短时间内从 0 跳到 0.35。这个跳变对应一个很宽的频谱成分,尤其是高频瞬态,人耳听起来就像咔哒声或爆音。即使波形本身记录了自然的声音起振过程,播放逻辑如果在任意位置硬切,也会带来同样的问题。
除了起止位置,循环音效中也会遇到类似情况。比如一段环境风声需要无缝循环,如果循环点两端的幅值不一致,每次回到循环点时就会产生周期性的滴答声。此时淡入淡出不能只用在整体播放上,还需要对循环片段做交叉淡化,让首尾采样互相叠加。很多音频中间件将这一功能称为循环交叉淡入,本质上仍然是在处理增益连续性。
另一个容易混淆的概念是直流偏移。直流偏移指的是波形整体偏离零轴,即使做了淡入,起始时刻仍然可能从某个非零直流值开始,造成轻微爆音。高质量音频编辑软件通常提供移除直流偏移的功能。对程序化生成的声音,也可以在写入缓冲区前先减去平均幅值,让波形围绕零轴对称。淡入淡出解决的是增益包络不平滑,直流偏移解决的是波形中心位置不正确,两者配合才能彻底消除起止噪声。
淡入淡出的实现:线性增益与指数增益
淡入淡出在数字域可以理解为对每个采样点乘以一个随时间变化的增益系数。假设淡入时长为 T,当前采样点序号为 n,线性淡入的增益公式为 g = n / T。当 n 从 0 增长到 T 时,g 从 0 增加到 1。线性方式实现简单,计算量很小,适合对性能要求较高的移动端场景。但人耳对响度的感知并不是线性的,线性增益在开头会显得变化较快,结尾则相对平缓,有时听感仍然不够自然。
指数增益更接近人耳感知特性。常见做法是使用 pow(n / T, k) 或 1 - exp(-k * n / T) 这样的曲线,其中 k 控制弯曲程度。指数淡入在开头变化较慢,随后迅速抬升,适合需要保留音头冲击感但又不想产生爆音的素材。需要特别注意的是,如果使用纯指数函数,增益在时间为 0 时通常不是严格意义上的 0,因此要先把初始增益设置成一个极小的非零值,例如 0.0001,再开始指数上升,避免出现数学计算上的无效值或听感上的停顿。
下面是使用 Web Audio API 的 GainNode 实现指数淡入的代码。音频上下文创建后,gain 参数通过 setValueAtTime 设置起始值,再用 exponentialRampToValueAtTime 平滑过渡到目标值。淡出的思路相同,只是把目标时间放在音频结束之前,让增益回到接近 0 的位置。
// 创建音频上下文与增益节点 const audioCtx = new AudioContext(); const gainNode = audioCtx.createGain(); gainNode.connect(audioCtx.destination); // 从 0.0001 开始,避免指数斜坡出现 0 值 gainNode.gain.setValueAtTime(0.0001, audioCtx.currentTime); // 30ms 内指数上升到 1 gainNode.gain.exponentialRampToValueAtTime(1.0, audioCtx.currentTime + 0.03); // 播放结束后,在 50ms 内指数淡出 gainNode.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + 2.0);
线性淡入更适合短促的 UI 音效,比如 10ms 到 30ms 的点击声。这个长度的淡入人耳不易察觉到音量变化,但足以消除爆音。对于音乐、环境音或者语音提示,淡入时间可以适当延长到 50ms 以上。淡出时间通常比淡入稍长,因为突然消失比突然出现更容易引起注意。不同项目可以根据素材特性做微调,但先把线性与指数两种曲线都实现出来,才有条件对比听感差异。
音量包络 ADSR:从音头到释放的完整控制
淡入淡出只覆盖声音开始和结束两个阶段,而很多音效需要更丰富的电平变化。例如一段合成器音色,可能先是快速上升的音头,然后衰减到持续电平,最后在按键释放后慢慢消失。这种完整的电平变化曲线称为音量包络,其中 ADSR 是最经典的模型。ADSR 分别代表 Attack、Decay、Sustain、Release 四个阶段。
Attack 控制从静音到达峰值电平的时间,Decay 控制从峰值回落到持续电平的时间,Sustain 是按键保持期间的稳定电平,Release 则是释放后从持续电平回到静音的时间。假设 attack 为 20ms,decay 为 80ms,sustain 电平为 0.6,release 为 150ms。声音触发后先快速升到 1.0,再用 80ms 降到 0.6,用户按住期间一直保持 0.6,松手后 150ms 内逐渐消失。包络曲线通常由音频引擎自动计算并应用到每个采样点,开发者只需要配置时间参数。
在实际播放采样素材时,也可以借鉴 ADSR 的思路手动实现。下面是一段 C# 协程代码,用 Unity 的 AudioSource 模拟 attack 与 release。它在播放开始时控制 volume 从 0 升到目标值,在结束前控制 volume 平滑回落,适合没有内置包络编辑器的轻量级音频播放模块。
IEnumerator PlayWithFade(AudioSource source, float fadeIn, float fadeOut, float hold)
{
float startVolume = 0f;
float targetVolume = 1f;
float timer = 0f;
source.volume = startVolume;
source.Play();
while (timer < fadeIn)
{
timer += Time.deltaTime;
source.volume = Mathf.Lerp(startVolume, targetVolume, timer / fadeIn);
yield return null;
}
yield return new WaitForSeconds(hold);
timer = 0f;
while (timer < fadeOut)
{
timer += Time.deltaTime;
source.volume = Mathf.Lerp(targetVolume, startVolume, timer / fadeOut);
yield return null;
}
source.Stop();
source.volume = targetVolume;
}
ADSR 包络也常用于程序化生成的声音。比如需要模拟脚步声、打击声或提示音时,可以先用振荡器生成基础波形,再用包络控制整体响度。这样生成出来的声音没有固定音频文件,体积更小,而且音色变化灵活。包络的时间参数会直接影响声音的性格:攻击时间极短的包络适合敲击类音色,攻击时间较长的包络适合弦乐或环境音。通过调整这几个参数,同一个基础波形可以变成完全不同的听感。
实战配置:曲线形状、时长与循环处理
淡入淡出不是时间越长越好。如果给一个只有 100ms 的 UI 音效加上 200ms 淡入,用户还没听到有效内容,声音就已经被音量曲线吞掉了。一般建议短音效的淡入控制在 5ms 到 30ms,淡出可以略长,约 20ms 到 80ms。人声提示和环境音可以单独调整,但原则是淡入不要遮盖音头特征,淡出不要在内容结束后还拖得太久。
曲线形状对听感的影响也很明显。线性曲线在中段变化均匀,但开头和结尾都显得有些机械。指数曲线在接近零的位置变化更细腻,听起来更像自然声响的起振与消失。有些音频引擎还提供对数曲线作为折中。若希望淡入几乎不可闻,可以先用极短线性段把增益从 0 提到 0.05,再接入指数段上升到 1.0,这样既避免了指数函数无法从零开始的问题,也保留了自然听感。
循环音效需要额外处理。一段雨声或风声通常只有几秒,播放时通过循环来延长持续时间。如果直接在循环点跳回开始位置,结束帧和开始帧的电平差会造成周期性爆音。解决方法是在循环点附近做交叉淡化:将结束部分的最后若干毫秒与开始部分的最初若干毫秒重叠相加,让两个方向的波形逐渐过渡。Web Audio 中可以通过 AudioBufferSourceNode 的 loop 属性配合自定义调度实现,而游戏音频中间件通常自带循环交叉淡化参数,使用时需要把循环点裁剪到波形过零附近,效果会更稳定。
除了播放时的淡入淡出,音频素材本身的预处理也很重要。导出音频时避免片段被硬切,尤其是人声和音乐,应在编辑阶段保留自然衰减或手动添加短淡出。混音阶段如果多个音效同时触发,突发的总电平叠加也可能造成削波失真。此时除了单个音效的包络控制,还需要在总线上设置限制器或动态压缩,把瞬时峰值控制在安全范围内。淡入淡出解决的是单条声源起止平滑,总线处理解决的是多条声源叠加后的整体动态,两者属于不同层级,但目标都是让最终听感自然、干净。
音量包络和淡入淡出并不是复杂的信号处理算法,却是音频体验中最容易被忽视的细节。一个游戏界面可能同时触发多个提示音,如果每条音效都做了 10ms 左右的淡入,整体听感就会柔和很多。将这些参数暴露到配置表或音频编辑器中,让设计师可以根据素材实时调整,会比在代码里写死数值更灵活。对于程序化音频和互动音乐,包络控制更是基础能力,值得在项目早期就纳入音频框架设计。

