繪制音頻的波形圖
當然,讀者可以把音頻文件拉到Adobe Audition或其它音頻分析軟件中,再使用它繪制出來的特征圖。
那有沒有辦法自己寫代碼來繪制,並做一些靈活的控制呢?
本文介紹通過python的matplotlib.pyplot來繪制波形圖。
pyplot是一個強大的繪圖庫,小程這裏用來繪制波形,也只是牛刀小試。
小程先用自己的話,解釋一些本文會提到的基礎概念。
聲道數,也叫通道數,這在采集聲音(錄制)時就引入的概念,可以理解為用幾個通道去錄制聲音。比如一個人唱歌時,可以在他的左前方跟右前方(與聲源同距離同方位),分別放一個通道去錄制,這時兩個通道錄到的數據很可能是接近或相同的,如果距離或方位不同,則錄制到的音頻就會有差別。為了真實地還原現場,可以考慮多個通道進行錄制,但也需要考慮錄制後播放時是否支持多通道(否則還是達不到效果)。一般來說,單聲道或雙聲道的音頻文件還是比較常見的。
采樣率,針對一個通道而言,也就是1秒鐘一個通道采集的樣本個數,各個通道各自采集。一般來說,常見的音頻文件的采樣率都比較高,比如44100Hz、32000Hz之類。讀者在看波形圖時,看到“一團一團”的波形,這跟采樣率很大有關,比如一秒內采集到4萬多個樣本,然後要在一秒的刻度上反映出這4萬個點的振動情況,都聚集在一起了。
位深,也叫量化精度、位寬,即對一個樣本的值用多少bit去表示它,用的bit越多,能表示的值就越多,也就越能接近樣本的原值。比如極端一點,用2個bit去表示,那能表示的值就只能是0、1、2、3,那不管樣本的值是多少,最終都會就近地選擇這4個值中的一個,這樣的話,所有樣本在量化後的層次就很少(就4個值,對應電平)。如果用16bit、24bit或更多的bit去量化樣本的值,層次就多得多,最終聽起來會更細膩,當然存儲的體積也更大。
幀數,也叫樣本個數。對於“總幀數”要根據上下文來判斷,有可能是一個通道(聲道)的總幀數,也有可能是所有通道的總幀數。對於樣本個數,是可以通過文件的大小與位深計算出來的。比如,對於一個pcm文件,已經知道文件大小是fs,采樣精度為w個字節(比如2個字節),那所有通道的樣本個數是(fs / w),如果是n個通道,則一個通道的樣本個數是(fs / w / n)。
波形圖,也叫振幅圖,是音頻的振幅(或能量)這個維度的圖形表達。對於波形圖,橫坐標是時間,縱坐標一般有兩種表示方式,一種方式是用dB來表示(就是分貝,讀者可以留意後面的截圖),audition就用dB來表示;另一種方式是用[-1, 1]這個範圍來表示,這種方式並不關心具體的能量值,只關心振幅的趨勢,所以用歸一化的思路固定一個變化的範圍就可以了。
介紹完這些枯燥的概念後,小程先擺一個繪制波形的代碼,再在後面做一些解釋。
import wave
import matplotlib
matplotlib.use(‘TkAgg‘)
import matplotlib.pyplot as plt
import os, sys
import audioread
import numpy as np
def decode2wav(srcname, outname):
f = audioread.audio_open(filename)
nsample = 0
for buf in f:
nsample += 1
f.close()
with audioread.audio_open(filename) as f:
print("input file: channels=%d, samplerate=%d, duration=%d" % (f.channels, f.samplerate, f.duration))
channels = f.channels
samplewidth = 2
samplerate = f.samplerate
compresstype = "NONE"
compressname = "not compressed"
outwav = wave.open(outname, ‘wb‘)
outwav.setparams((channels, samplewidth, samplerate, nsample, compresstype, compressname))
for buf in f:
outwav.writeframes(buf)
outwav.close()
def pcm2wav(srcname, outname, channels, samplewidth, samplerate):
fs = os.path.getsize(srcname)
nsample = fs / samplewidth
outwav = wave.open(outname, ‘wb‘)
outwav.setparams((channels, samplewidth, samplerate, nsample, "NONE", "not cmopressed"))
fsrc = open(srcname, ‘rb‘)
outwav.writeframes(fsrc.read())
fsrc.close()
outwav.close()
if __name__ == ‘__main__‘:
filename = sys.argv[1]
filename = os.path.abspath(os.path.expanduser(filename))
if not os.path.exists(filename):
print("input file not found, then exit")
exit(1)
path, ext = os.path.splitext(filename)
wavpath = path + ".wav"
if ext != ‘.wav‘:
if ext == ".pcm":
if len(sys.argv) < 5:
print("when input pcm, parameters should be [pcmfilename, channelcount, samplewidth_byte, samplerate]")
exit(1)
chcout = int(sys.argv[2])
bitwidth = int(sys.argv[3])
samplerate = int(sys.argv[4])
pcm2wav(filename, wavpath, chcout, bitwidth, samplerate)
else:
decode2wav(filename, wavpath)
wav = wave.open(wavpath, ‘rb‘)
channels, samplewidth, samplerate, nframe = wav.getparams()[:4]
print("in wav file params: (%d:%d:%d:%d)" % (channels, samplewidth, samplerate, nframe))
audiobyte = wav.readframes(nframe)
wav.close()
time = np.arange(0, nframe) * (1.0 / samplerate)
numdata = np.fromstring(audiobyte, dtype=np.int16)
numdata = numdata * 1.0 / max(abs(numdata))
numdata = np.reshape(numdata, (nframe, channels))
# plt.figure()
for i in range(channels):
plt.subplot(channels*2-1, 1, i*2+1)
plt.plot(time, numdata[:, i])
plt.xlabel("times(s)")
plt.ylabel("amplitude")
plt.title("wave - channel %d" % (i+1))
plt.show()
大體的思路是先把音頻文件解碼出pcm數據並寫成wav文件,再使用pyplot對wav文件進行繪制(實際只需要繪制樣本歸一化後的值)。
小程先展示下執行的效果。
可以這樣執行這個腳本,分別輸入wav、mp3、pcm與flac文件:
運行後可以得到相應的波形圖,這裏提供幾個截圖。
*. 單聲道的波形:
對應,audition分析到的波形是這樣的:
*. 雙聲道的波形:
對應,audition分析到的波形是這樣的:
然後,小程對關鍵的代碼做一些解釋,請參考下面的截圖。
其中,pyplot的函數subplot(),作用是畫子圖。subplot(rownum, clonum, curnum),前兩個參數指定畫多少行多少列,最後一個參數是當前子圖的編號,按從左往右,從上往下的順序進行編號。
比如:
plt.subplot(2, 1, 1) -- 畫兩行一列(兩個子圖),在第一個子圖繪制。
numpy的函數reshape(),作用是給原數組一個新的形狀,也就是重新定義行列數,但不改變數組的值。
比如:
numdata = np.reshape(numdata, (nframe, channels)) -- 把numdata改為nframe行,channels列。
至此,通過pyplot繪制波形圖的實現介紹完畢了。
總結一下,本文介紹了如何通過pyplot來繪制音頻波形圖的辦法,講解了一些概念,也解釋了代碼上的實現。從代碼實現以及概念的理解的角度來說,難度系數為3。
繪制音頻的波形圖