多媒體開發(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。