1. 程式人生 > >繪制音頻的波形圖

繪制音頻的波形圖

pyplot python 音頻 波形 振幅

有時候,為了直觀地分析音頻的特征,畫幾個圖是必不可少的。

當然,讀者可以把音頻文件拉到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。

繪制音頻的波形圖