続DTMF 解析

また例の奴を書き続けていたのですが・・・

今度はもう少し数値的にDTMF を解析するように変更しました。

ソレと、各種オーディオフォーマットに対応しました。

 

まず、チャンネル数が1 の場合はモノラルで、2 の場合はステレオです。

そして、ビットレートが8Bit か16Bit かを選択が出来ます。

その為、データ格納変数を4種類用意ました。

    Private _AudioIn_M8_ReadData() As Byte
    Private _AudioIn_M16_ReadData() As Int16
    Private _AudioIn_S8_ReadData() As Int16
    Private _AudioIn_S16_ReadData() As Int32

コレは、そのままM8 がモノラル8Bit で、S16 はステレオ16 Bit と言う意味ですが・・・

モノラルの8Bit はByte で、16Bit はInt16 でそのままの配列ですね。

ステレオの場合、1 データに対して、左チャンネル、右チャンネルとデータが書かれますので、

8Bit の場合、左と右用で16Bit を用意します。

16Bit の場合は、同じ事で32Bit ですね。

ちなみに、1つの配列の要素に、左右でデータ上位下位に分かれています。

Byte ストリーミングは左右左右左右とデータが流れるので、

配列要素には、下位8Bit(16Bit) 左音声、上位8Bit(16Bit) 右音声となっております。

実際のデータは、8Bit データの場合 0 ~ 255 の値で、128 が無音(0) として扱われます。

16Bit データの場合 -32768 ~ 32767 で、0 が無音として扱われます。

コレを、FFT 用のバッファに移して音声データを周波数変換します。

 

        If _WAVFMTEX.nChannels = 1 And _WAVFMTEX.wBitsPerSample = 8 Then
            'Monaural 8 Bit
            For i = 0 To AudioInBufferNum - 1
                _AudioIn_M_FftData(i) = (CType(_AudioIn_M8_ReadData(i), Double) - 128.0)
            Next
            Four1(_AudioIn_M_FftData, FFT_FORWARD)
        ElseIf _WAVFMTEX.nChannels = 1 And _WAVFMTEX.wBitsPerSample = 16 Then
            'Monaural 16 Bit
            For i = 0 To AudioInBufferNum - 1
                _AudioIn_M_FftData(i) = CType(_AudioIn_M16_ReadData(i), Double)
            Next
            Four1(_AudioIn_M_FftData, FFT_FORWARD)
        ElseIf _WAVFMTEX.nChannels = 2 And _WAVFMTEX.wBitsPerSample = 8 Then
            'Stereo 8 Bit
            For i = 0 To AudioInBufferNum - 1
                Dim R_HiBit As Integer = _AudioIn_S8_ReadData(i) And &HFF
                Dim L_LowBit As Integer = (_AudioIn_S8_ReadData(i) >> 8) And &HFF
                _AudioIn_R_FftData(i) = (CType(R_HiBit, Double) - 128.0)
                _AudioIn_L_FftData(i) = (CType(L_LowBit, Double) - 128.0)
            Next
            Four1(_AudioIn_R_FftData, FFT_FORWARD)
            Four1(_AudioIn_L_FftData, FFT_FORWARD)
        ElseIf _WAVFMTEX.nChannels = 2 And _WAVFMTEX.wBitsPerSample = 16 Then
            'Stereo 16 Bit
            For i = 0 To AudioInBufferNum - 1
                Dim R_HiBit As Integer = _AudioIn_S16_ReadData(i) And &HFFFF
                Dim L_LowBit As Integer = (_AudioIn_S16_ReadData(i) >> 16) And &HFFFF
                _AudioIn_R_FftData(i) = CType(R_HiBit, Double)
                _AudioIn_L_FftData(i) = CType(L_LowBit, Double)
            Next
            Four1(_AudioIn_R_FftData, FFT_FORWARD)
            Four1(_AudioIn_L_FftData, FFT_FORWARD)
        Else
            Return False
        End If

 

基本的に、普通のwaveIn 関係の処理に、上記の様な処理を追加すれば複数フォーマットに対応できると思われます。

 

さて、サンプリングレートがメンドクサイのですが・・・

入力デバイスのサンプリングレートによって、周波数分解能が変わります。

私のPC では、下記三種類のサンプリングレートが選択できます。

 11.025 kHz

 22.050 kHz

 44.100 kHz

計算上では、この半分の数値が再生周波数になると思うのですが、

※本来はフィルタが有る為、上下20Hz 程切られてると思うが・・・

11.025 kHz の場合、一秒間に11025 データです。

コレに対し、waveIn で取得するデータ数は下記で定義してあります。

    Private Const AudioInBufferNum As Integer = 1024

1024 個のデータをFFT にかけると、実数部512 、虚数部512 個のデータが出来上がります。

512 個のデータの内、半分が対象のデータとなるので、256 個のデータが周波数成分になります。

1024 / 256 = 4 ですので、周波数レンジは、11025 / 4 = 2756.25 ≒ 2.8 kHz

2800 / 256 = 10.93 Hz の分解能と言う事になります。

DTMF の各周波数に合わせて、データ分解能を計算します。

    Private Const Hz697 As Integer = 697
    Private Const Hz770 As Integer = 770
    Private Const Hz852 As Integer = 852
    Private Const Hz941 As Integer = 941

    Private Const Hz1209 As Integer = 1209
    Private Const Hz1336 As Integer = 1336
    Private Const Hz1477 As Integer = 1477

    Private _HiHz As Integer
    Private _LowHz As Integer
    Private _WaveDataRange As Double

    Private _Hz697Column As Integer
    Private _Hz770Column As Integer
    Private _Hz852Column As Integer
    Private _Hz941Column As Integer

    Private _Hz1209Column As Integer
    Private _Hz1336Column As Integer
    Private _Hz1477Column As Integer

        _WaveDataNum = AudioInBufferNum / 4
        _HiHz = _WAVFMTEX.nSamplesPerSec / 4
        _LowHz = 0
        _WaveDataRange = _WaveDataNum / (_HiHz - _LowHz)

        _Hz697Column = Hz697 * _WaveDataRange
        _Hz770Column = Hz770 * _WaveDataRange
        _Hz852Column = Hz852 * _WaveDataRange
        _Hz941Column = Hz941 * _WaveDataRange

        _Hz1209Column = Hz1209 * _WaveDataRange
        _Hz1336Column = Hz1336 * _WaveDataRange
        _Hz1477Column = Hz1477 * _WaveDataRange
 

コレで、FFT 後、値を周波数変換したデータ配列(この場合は256 個の配列)内の、

何処の値がDTMF のどの周波数かが解る。

んで、その値を使って下記の様に配列からデータを取り込む。

※この場合は、ターゲットの上下1個ずつ、計3個のデータを取って、その平均値を使って解析している。

        Dim Hz697 As Double = (_WaveDataM(_Hz697Column - 1) + _WaveDataM(_Hz697Column) + _WaveDataM(_Hz697Column + 1)) / 3.0
        Dim Hz770 As Double = (_WaveDataM(_Hz770Column - 1) + _WaveDataM(_Hz770Column) + _WaveDataM(_Hz770Column + 1)) / 3.0
        Dim Hz852 As Double = (_WaveDataM(_Hz852Column - 1) + _WaveDataM(_Hz852Column) + _WaveDataM(_Hz852Column + 1)) / 3.0
        Dim Hz941 As Double = (_WaveDataM(_Hz941Column - 1) + _WaveDataM(_Hz941Column) + _WaveDataM(_Hz941Column + 1)) / 3.0
        Dim Hz1209 As Double = (_WaveDataM(_Hz1209Column - 1) + _WaveDataM(_Hz1209Column) + _WaveDataM(_Hz1209Column + 1)) / 3.0
        Dim Hz1336 As Double = (_WaveDataM(_Hz1336Column - 1) + _WaveDataM(_Hz1336Column) + _WaveDataM(_Hz1336Column + 1)) / 3.0
        Dim Hz1477 As Double = (_WaveDataM(_Hz1477Column - 1) + _WaveDataM(_Hz1477Column) + _WaveDataM(_Hz1477Column + 1)) / 3.0

後は、それぞれの周波数の数値が、基準より上かどうかを判断し、DTMF が入力されたか判断している。

※この場合は、全周波数の平均値を取り、その値に倍率をかけたモノを閾値としている。

 図の中の赤線がその値。

        If Hz697 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz697Flag = True
        End If
        If Hz770 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz770Flag = True
        End If
        If Hz852 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz852Flag = True
        End If
        If Hz941 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz941Flag = True
        End If
        If Hz1209 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz1209Flag = True
        End If
        If Hz1336 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz1336Flag = True
        End If
        If Hz1477 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz1477Flag = True
        End If

        _Flag_1 = Hz697Flag And Hz1209Flag
        _Flag_2 = Hz697Flag And Hz1336Flag
        _Flag_3 = Hz697Flag And Hz1477Flag
        _Flag_4 = Hz770Flag And Hz1209Flag
        _Flag_5 = Hz770Flag And Hz1336Flag
        _Flag_6 = Hz770Flag And Hz1477Flag
        _Flag_7 = Hz852Flag And Hz1209Flag
        _Flag_8 = Hz852Flag And Hz1336Flag
        _Flag_9 = Hz852Flag And Hz1477Flag
        _Flag_KM = Hz941Flag And Hz1209Flag
        _Flag_0 = Hz941Flag And Hz1336Flag
        _Flag_SP = Hz941Flag And Hz1477Flag

 

ソース内の特定部位しか書いて無いので意味不明な記事ですが・・・

後は適当にグラフはPictureBox に

        Dim BMPImage As Bitmap
        Dim WaveGraphics As Graphics
        BMPImage = New Bitmap(PictureBox1.Width, PictureBox1.Height)
        WaveGraphics = Graphics.FromImage(BMPImage)

        For i = 0 To _WaveDataNum - 1
            Dim XScl As Integer = (PictureBox1.Width / _WaveDataNum) * i
            Dim YLen As Integer = (PictureBox1.Height / _WaveDataM_MaxVal) * _WaveDataM(i)
            If YLen > PictureBox1.Height Then
                YLen = PictureBox1.Height
            End If
            WaveGraphics.DrawLine(Pens.Blue, XScl, PictureBox1.Height - 10, XScl, PictureBox1.Height - YLen - 10)
        Next
        Dim AveLen As Integer = (PictureBox1.Height / _WaveDataM_MaxVal) * (_WaveDataM_AveVal * JudgeAveAmp)
        WaveGraphics.DrawLine(Pens.Red, 0, PictureBox1.Height - AveLen - 10, PictureBox1.Width, PictureBox1.Height - AveLen - 10)

こんな事して絵を描いているだけですね^^;

ちなみに、縦線は、それぞれDTMF の周波数を緑と紫で線をひいています。

サンプリングレートを上げる場合は、もちろん周波数分解能が低くなるので、

取得データ数を増やす必要が有ります。

処理速度を考えると、やはり11.025 kHz が安定してDTMF 検出出来る感じです。

 

結局、このプログラムで週末を完全に潰してしまった^^;

なんだかなぁ~

この部分だけソース公開するか、全部合わせてフリーソフトにするかはまた検討します。

※なにがしAPI の関係でこのソフトをどうするか検討中です^^;

 

さて、寝るぞぉ~

Scskpbx2

コメント(3)