基本上,如今经常使用的声音采样办法是pcm,而对于压缩音频的解码,获得的也pcm数据。这个pcm数据,只是一堆数值,有正有负,看这个值看不出什么花样。code
声音采集,采的是什么呢?blog
采的是声音的强度变化,也是声音这种能量的强弱变化,这种强弱用分贝来表示,即dB。因此,pcm数据跟这个dB就必定有关系,这个关系是这样的:
dB=20∗log10(pcm)
pcm=pow(10,(dB/20.0))it
模数转换ADC时经常使用的位深是16bit,也就是用16位来表示一个sample,这里不考虑偷懒而使用不足16位的状况。16位能表示65536个值,也就意味着有65536个dB能够表示出来,哪又怎么样?很厉害了吗?io
的确是比较厉害的了。test
16位的pcm数值,分正负,那正数的范围是0至32767,负数的范围是-1至-32768,随便挑几个来看看,对应的dB是多少,以下图:
import
由上面的运算可知,16位的pcm值,若是不分正负,最大能够表示96dB,若是分正负,也能表示到90dB。90dB是什么概念?有数据代表(我也不清楚什么数据),85dB就会伤害了你,90dB至关于摩托车启动的声音--你有开过吗?音频
因此,你的pcm数据须要去到90dB以上吗?想祸害谁?通常状况下,能表示到90dB就很够用了。file
既然知道了pcm数值与dB的关系,就能够搞点事情了,好比把pcm转成dB后再放大一点,再保存成新的文件,是否是播放就能够大声一点了呢?channel
来作个实验。float
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。