共计 1689 个字符,预计需要花费 5 分钟才能阅读完成。
基本上,当初罕用的声音采样方法是 pcm,而对于压缩音频的解码,失去的也 pcm 数据。这个 pcm 数据,只是一堆数值,有正有负,看这个值看不出什么花色。
声音采集,采的是什么呢?
采的是声音的强度变动,也是声音这种能量的强弱变动,这种强弱用分贝来示意,即 dB。所以,pcm 数据跟这个 dB 就肯定有关系,这个关系是这样的:
dB=20∗log10(pcm)
pcm=pow(10,(dB/20.0))
模数转换 ADC 时罕用的位深是 16bit,也就是用 16 位来示意一个 sample,这里不思考偷懒而应用有余 16 位的状况。16 位能示意 65536 个值,也就意味着有 65536 个 dB 能够示意进去,哪又怎么样?很厉害了吗?
确实是比拟厉害的了。
16 位的 pcm 数值,分正负,那负数的范畴是 0 至 32767,正数的范畴是 - 1 至 -32768,轻易挑几个来看看,对应的 dB 是多少,如下图:
由下面的运算可知,16 位的 pcm 值,如果不分正负,最大能够示意 96dB,如果分正负,也能示意到 90dB。90dB 是什么概念?有数据表明(我也不分明什么数据),85dB 就会挫伤了你,90dB 相当于摩托车启动的声音 – 你有开过吗?
所以,你的 pcm 数据须要去到 90dB 以上吗?想祸患谁?个别状况下,能示意到 90dB 就很够用了。
既然晓得了 pcm 数值与 dB 的关系,就能够搞点事件了,比方把 pcm 转成 dB 后再放大一点,再保留成新的文件,是不是播放就能够大声一点了呢?
来做个试验。
import math
import math
import wave
import audioread
import contextlib
import sys
import math
import struct
def gainpcm(filepath):
try:
with audioread.audio_open(filepath) as f:
with contextlib.closing(wave.open(filepath+'.wav', 'w')) as of:
of.setnchannels(f.channels)
of.setframerate(f.samplerate)
of.setsampwidth(2)
for buf in f:
for i in range(0, len(buf)-2, 2):
s = buf[i] + buf[i+1]
pcm = struct.unpack('<h', s)[0]
apcm = abs(pcm)
if apcm==0:
apcm=1
db = 20*math.log10(apcm)
db = db * 1.2
apcm=int(math.pow(10, float(db)/20.0))
if apcm>32767:
apcm=32767
if pcm < 0:
pcm=-apcm
else:
pcm = apcm
tbuf = struct.pack('<h', pcm)
of.writeframes(tbuf)
except audioread.DecodeError:
print("File could not be decoded.")
sys.exit(1)
if __name__ == '__main__':
gainpcm('test.mp3')
这里是对代码的简略解释:
把一个 mp3 放过来试验,进去一个 wav,发现 wav 文件的声音真的大了好多(好多是因为这里设置了 db*1.2)。然而,另一个问题也裸露进去了,就是听起来声音失真很重大了,这是因为放大到这个水平,很多 apcm 都超过了 32767,也就是人们说的截顶失真了,看一下原文件与放大后的文件波形图就更分明了:
因为不能上传音频,这里就不提供间接的音频比照了。
由此可见,要想不失真,那 db 就不要乘那么大的数啊 – 另一个方法:给它限幅(压幅),或者间接应用更适合的整体技术(灵便变音量跟限幅都思考进去了)比方 agc 或 drc 等,显著这个不是这里的内容。
好了,总结一下,本文演示了扭转音量的一种最原始的方法,就是间接改 pcm 的值(转 dB 再扭转,其实也能够间接扭转 pcm 值),但这不是最好的方法,因为它会引入失真的副作用。而从 pcm 数据中提取出能量(dB),这个也更像是音频特色的技能。有缘再见,see you。