Python Wave音源の周波数と音量(デシベル)を取得する方法[python音声解析 フーリエ変換 FFT]

Python
この記事は約11分で読めます。
スポンサーリンク

Wave音源の取得

音源の取得は以下の関数で行っています。
前回の書き込みの時と同様に引数で、開くファイルのパスを渡しています。
処理の流れとしてはファイルの制御と同じように、以下のような流れで処理を行います。

①ファイルのオープン
② データの取得
③ ファイルのクローズ

def read_wavefile(file_path):
    wf        = wave.open(file_path, 'rb')                   <- ファイルをオープン
    frames   = wf.getnframes()                               <- トータルのフレーム数を取得 
    Channels = wf.getnchannels()                             <- チャンネル数を取得
    sampWidth = wf.getsampwidth()                            <- データの幅取得
    framerate = wf.getframerate()                            <- フレームレートを取得
    print( str(frames) + " Frames")
    print( str(Channels) + " channels")
    print( str(sampWidth*8) + " bit" )
    print( str(framerate) + " hz" )
    print("\n")

    wf.setpos(int(frames/2))                                <- データを取得する位置を指定
    buf = wf.readframes(FLAME_SIZE)                         <- 指定サイズデータを取得
    wf.close()                                              <- ファイルをクローズ
    return buf

周波数取得処理

以下の関数で周波数を取得しています。
申し訳ありませんが、私はこういった計算処理がちょっと苦手で理解できていないので、ざっくり説明です。。。
あまり詳しくないので、コードで簡単に注釈しておきます。
詳細が知りたい方は、別の専門的なサイトを見てみてください。。。

def get_freq(data):
    data = np.frombuffer(data, dtype= "int16") / 32768.0   <- データが正規化
    spectrum = np.fft.fft(data)                            <- データのスペクトル?を取得
    maxvalue = 0
    maxidx = 0
    tmpvalue = 0
    flist = np.fft.fftfreq(FLAME_SIZE, d=1.0/44100)        <- 周波数のリストを生成

    for j in range( int( len(spectrum) / 2 ) ):
        tmpvalue = spectrum[j]
        if tmpvalue > maxvalue:                     <- 最も大きい値となっている部分を抽出
            maxvalue = tmpvalue          
            maxidx = j

    print( str(int(flist[maxidx])) + "Hz" )         <- 周波数を出力

私は、フーリエ変換についてよく理解できていませんが、各周波数帯を並べて、どこにデータが一番あるのかを見てそこがこの音声の周波数だ!って言っています。
うーん。原理が理解できない。。。

続いて音量の出力です。

スポンサーリンク

音量(デシベル)の出力

以下で、音量を取得しています。
流れ的には以下の通りです。

①累積2乗和を算出
②平均平方根を算出
③2乗平均平方を取得 <-これが入力信号レベル

最後に、 ③で取った値のlog10を取って20をかければ、デシベルになるらしいです。

def get_db(data):
    squaressum = 0
    # 累積二乗和を算出
    for i in range(FLAME_SIZE):
        squaressum += data[i] * data[i]
        
    # 平均平方根を算出
    meansquare = squaressum / (FLAME_SIZE/2)

    # 二乗平均平方を取得  (入出力信号レベル)
    rms = math.sqrt(meansquare)
    decibel = 20 * math.log10(rms)
    print( str( int(decibel) ) + "db" )

私には、なぜこれでデシベルが出せるのか全く理解できませんが、これでだせます。

これで、音声の周波数と音量を取得できました。
うまく組み込めば 長時間の無人試験なんかで、音がとまってないか・異音がなってないかといったことに活用できます。
私は、Pythonではありませんが、C# .net で同様の環境を構築し、無人の音声確認 試験環境を構築し試験の効率化をしていたことがあります。

みなさんも一度やってみてください。

ちなみに2つ以下と組み合わせれば、リアルタイムで波形を出力しながら、周波数と音量を確認するなんてことも可能ですなので、やってみてください。

スポンサーリンク

2021/12/21 追記
質問がありましたので、ステレオの音声をLRに分けて周波数を取得するサンプルプログラムを追記します。

デジタル音声では、LとRのデータは連続していますので、sampleWitdh単位でデータを分割すれば、LとRのチャンネルにデータを分けることができます。
今回の場合では、16Bit音声ですので、2Byte単位でデータを分離していきます。
そこまでできれば分離したデータをそれぞれ、単純にFFTすれば周波数が取得できます。

import sys
import wave 
import numpy as np
import math

FLAME_NUM  = int(8192)
DATA_NUM    = int(FLAME_NUM*4)  # Stereo 16Bit *  FLAME_NUM Frames
    
def read_wavefile(file_path):
    wf        = wave.open(file_path, 'rb')
    frames   = wf.getnframes()      # フレーム数を取得
    Channels = wf.getnchannels()
    sampWidth = wf.getsampwidth()
    framerate = wf.getframerate()
    print( str(frames) + " Frames")
    print( str(Channels) + " channels")
    print( str(sampWidth*8) + " bit" )
    print( str(framerate) + " hz" )
    
    print("\n")
    wf.setpos(int(frames/2))   # 全フレームの真ん中に位置を移動
    buf = wf.readframes(FLAME_NUM)  # セットした位置からFLAME_SIZEフレームを取得
    wf.close()
    
    lbuf = bytearray()
    rbuf = bytearray()
    #データをLChとRChに分離
    for i in range(0 ,DATA_NUM):
        if i%4 == 0 or i%4 == 1:
            lbuf.append(buf[i])
        if i%4 == 2 or i%4 == 3:
            rbuf.append(buf[i])
            
    return lbuf,rbuf

def get_freq(data):
    ndata = np.frombuffer(data, dtype= "int16") / len(data)
    spectrum = np.fft.fft(ndata)
    maxvalue = 0
    maxidx = 0
    tmpvalue = 0
    flist = np.fft.fftfreq(FLAME_NUM, d=1.0/44100)             # 周波数リスト
    
    for j in range( int( len(spectrum)/2 ) ):
        tmpvalue = spectrum[j]
        if tmpvalue > maxvalue:
            maxvalue = tmpvalue
            maxidx = j

    print( str(int(flist[maxidx])) + "Hz" )


if __name__ == '__main__':
    args = sys.argv
    
    if len(args) != 1:
        #保存実行
        
        wave_ldata,wave_rdata = read_wavefile( args[1] )
        get_freq(wave_ldata)
        get_freq(wave_rdata)

Pythonでマイク入力音声をリアルタイムで波形(グラフ)表示する方法【Windows、Pyaudio、matplotlib】
今回は、pythonでマイク音声を入力する方法について記載しようと思います。筆者は組み込みエンジニアでオーディオ関連の仕事をすることもあるので、Visual Studio C++で、音声入力とフーリエ変換をするプログラム書いたことがあり、ちょっと興味があって今回挑戦してみました。ラ...

Pythonについて勉強したい人は以下がおすすめです。私も持っていてたまに眺めて勉強していますものですのでぜひ購入して学習してみてください。

コメント

タイトルとURLをコピーしました