基本上,如今經常使用的聲音採樣辦法是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。