PyAudio 输出音频在开始或结束时为何会爆音?
结论
爆音更可能来自 paUInt8 样本的取值方式。仅凭这段代码无法判断是否存在 PyAudio 或库冲突。8-bit 无符号 PCM 的静音值是 128,而代码生成的值在 0 到 4 左右,离静音中心很远。开始播放或停止时,样本值突然变化会产生直流偏移和瞬态,听起来像爆音。
将波形映射到无符号 PCM
先将波形限制在 -1.0 到 1.0,再映射到以 128 为中心的样本值。脉冲波可以这样转换:
def to_u8(sample, gain):
# sample 应在 -1.0 到 1.0 之间;gain 表示峰值振幅
value = round(128 + sample * gain)
return max(0, min(255, value))
# pulseswitch 为 0 或 1 时,分别生成负、正半周
sample = 1.0 if pulseswitch else -1.0
data.append(to_u8(sample, volume))
三角波、锯齿波等当前以 0.0 到 1.0 表示的波形,应先转换为以零为中心的范围,再进行编码:
sample = 2.0 * tritone - 1.0
data_value = to_u8(sample, volume)
用对应的编码逻辑替换示例中的 data.append(...) 即可。注意,第二段示例中 data_value 这一行前面多了空格;实际写入时应使用 data.append(data_value)。所有波形模式都应使用一致的映射方式。无符号 8-bit PCM 的样本值必须限制在 0 到 255 之间。
在开始和结束时平滑振幅
样本回到静音时应恢复为 128,避免在波形的任意相位直接停止。可以在短时间内逐渐增加振幅来淡入,并在退出前逐渐减小振幅来淡出。每个样本都可以按淡入淡出系数调整 gain;当 gain 降到 0 时,输出值就是 128。
当前退出逻辑每轮将 volume 乘以 0.9,但这本身不能保证平滑结束。需要确认衰减后的样本使用新音量写入音频流,并在停止流之前让输出回到静音值。淡入淡出的时长和增益大小可以根据听感调整。现有信息不足以确定适用于所有设备的固定时长。
注意事项
volume = 4 是很小的峰值振幅设置,无法替代对 PCM 静音中心的处理。paUInt8 使用无符号样本;如果改用其他采样格式,应按该格式的编码范围和静音值进行映射。排查时,可以先只播放一个以 128 为静音值、以零为中心的简单波形,再逐步加入淡入淡出逻辑。
备注:内容仅供参考。
