音ファイルを取り込み画面にグラフ波形表示するサンプル( vb.net)

音ファイル(拡張子:Wav)をダウンロードする

音ファイルは純音ファイル(正弦波の周波数10kHz)を利用。
音が聞こえない人は耳年齢は60才以上です。以下を再生して聞こえますか?

再生時は音量に気をつけて下さいね。


上の右端スピーカー横にある所をクリックすると音ファイルをダウンロードできます。

 

上記方法でダウンロード出来ない場合は、下記の “音ファイルダウンロード” リンク上で右クリックして、”名前を付けてリンク先を保存(K)”をクリックしてください。

音ファイルダウンロード

 
ダウンロード出来たら、次はWavファイルのフォーマットを確認します。プログラム制作時に何をやっているか分からなるので、WAVファイルのデータ構造を知る必要があります。

WAVファイルデータ構造(フォーマット)を確認する

項目 サイズ (byte) データ部
チャンク識別子 4 “RIFF”(0x52494646)で固定。
チャンク サイズ 4 ファイル全体サイズからRIFFとWAVEのバイト数(8Byte)を引いた数
フォーマット 4 WAVファイルの場合は“WAVE”(0x57415645)で固定。AVIファイルの場合は“AVI”が入る
サブチャンク①識別子 4 “fmt “(0x666D7420)で固定。
サブチャンク①サイズ 4 16 + 拡張パラメータのサイズ。デフォルト値は16
音声フォーマット 2 圧縮のフォーマットIDを表す。非圧縮のリニアPCMは1
 0x0001:リニアPCM
 0x0002:MS ADPCM
 0x0005:IBM CSVD
 0x0006:A-Law
 0x0007:μ-Law
上記以外はこちらを参照
チャンネル数 2 モノラルは1、ステレオは2
サンプリング周波数
[byte/sec]
4 8kHzの場合は8000、44.1kHzの場合なら44100
44.1kHz 16bitステレオの場合:44100×2×2 =176400
44.1KHz 16bitモノラルの場合:44100×2×1=88200
1 秒あたりバイト数の平均 4 サンプリング周波数 * ブロックサイズで求める
ブロックサイズ 2 チャンネル数 * 1サンプルあたりのビット数 / 8で求める。ステレオ16bitなら16bit*2 = 32bit = 4byte
バイト/サンプル 2 サンプルあたりのバイト数
 16bitステレオ:4
 16bitモノラル:2
 8bitモノラル:1
拡張パラメータのサイズ (2) リニア PCM (音声フォーマットが1) の場合は未使用。
拡張パラメータ (*) リニア PCM (音声フォーマットが1) の場合は未使用。
サブチャンク② 識別子 4 “data” (0x64617461)で固定。
サブチャンク② サイズ 4 波形データのバイト数(総ファイルサイズ – 126)
データ * 波形データを格納。リニアPCMの場合は時間順に格納される。ステレオは左→右→左→右…のように格納される。8ビットの場合は符号無し整数 (0 – 255)、16ビットの場合は符号付き整数 (-32768 – 32767) で表わす。

 

Wavファイルの構造をもっと知りたい方はこちら(音ファイル(拡張子:WAVファイル)のデータ構造について)に記載しています。ご参考にどうぞ。

音ファイルを読み込むプログラムを作成する(VB.net)

ここから本題に入ります。以下の順番で作ります。

  • プロジェクトを作る
  • ヘッダー部を読み込む処理
  • フォームを作成してChartオブジェクトを貼る
  • データ部を読み込み波形を作る処理
  • 音ファイルを取り込んで画面に波形グラフを表示する

プロジェクトを作る

Visual Studioで何か適当なプロジェクトを作ります。ここではVisual Studio 2015を使用しています。Visual BasicのWindowsフォームアプリケーションを選択します。

ヘッダー部を読み込む処理を制作

Imports System.IO
Imports System.Text.Encoding

Public Class clsHeader

    ' riffチャンク
    Public strRiff As String
    Public intChunkSize As Integer
    Public strFileFormat As String

    ' fmtチャンク
    Public strFmt As String
    Public intFmtSize As Integer
    Public shFmtFormat As Short
    Public shChannel As Short
    Public intSampleRate As Integer
    Public intBytePerSec As Integer
    Public shBlockSize As Short
    Public shBitPerSample As Short

    Public PlayTime As Integer
    Public strWork As String

    ''' 
    ''' Wavファイルヘッダー読み込み処理
    ''' 
    ''' 
    ''' 
    Public Sub readHeader(ByRef fs As FileStream, ByRef br As BinaryReader)

        Try

            ' ======================
            ' RIFFヘッダー読み込み
            ' ======================
            strRiff = GetEncoding(20127).GetString(br.ReadBytes(4))
            intChunkSize = BitConverter.ToInt32(br.ReadBytes(4), 0)
            strFileFormat = GetEncoding(20127).GetString(br.ReadBytes(4))

            If (strRiff.ToLower() <> "riff") Then
                MessageBox.Show("riff chunk doesn't exists.")
                Exit Sub
            End If

            If (strFileFormat.ToLower() <> "wave") Then
                MessageBox.Show("format isn't wave format.")
                Exit Sub
            End If

            ' ======================
            ' fmtチャンクの読み込み
            ' ======================
            strFmt = GetEncoding(20127).GetString(br.ReadBytes(4))

            If (strFmt.ToLower() <> "fmt ") Then
                MessageBox.Show("fmt chunk doesn't exists.")
                Exit Sub
            End If

            intFmtSize = BitConverter.ToInt32(br.ReadBytes(4), 0)
            shFmtFormat = BitConverter.ToInt16(br.ReadBytes(2), 0)
            shChannel = BitConverter.ToInt16(br.ReadBytes(2), 0)
            intSampleRate = BitConverter.ToInt32(br.ReadBytes(4), 0)
            intBytePerSec = BitConverter.ToInt32(br.ReadBytes(4), 0)
            shBlockSize = BitConverter.ToInt16(br.ReadBytes(2), 0)
            shBitPerSample = BitConverter.ToInt16(br.ReadBytes(2), 0)


            ' ================================
            ' dataチャンクを見つけるまでループ
            ' ================================
            Dim blnFindD As Boolean = False
            Dim intWorkSize As Integer = 0

            While blnFindD = False

                ' 拡張部分を考慮して2byteずつ取得
                strWork = GetEncoding(20127).GetString(br.ReadBytes(2))

                If strWork = "da" Then
                    'dataチャンクの頭2文字が含まれている事を確認
                    strWork = GetEncoding(20127).GetString(br.ReadBytes(2))

                    If strWork = "ta" Then
                        blnFindD = True
                    End If
                End If

            End While

        Catch ex As Exception
            MessageBox.Show("readHeader:" & ex.ToString())
        End Try

    End Sub

End Class

フォームを作成してChartオブジェクトを貼る

チャートライブラリ(System.Windows.Forms.DataVisualization)を参照追加する。

チャートオブジェクトをFormに貼り付ける。

データ部を読み込み波形を作る処理


Imports System.IO
Imports System.Windows.Forms.DataVisualization.Charting

Public Class Form1

    Dim intDataSize As Integer

    Dim strFilePath As String = "C:\Users\owner\Desktop\wav\sample_2000msec.wav"

    ''' 
    ''' フォームロード
    ''' 
    ''' 
    ''' 
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load


        Try

            Using fs = New FileStream(strFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)

                Dim br As BinaryReader = New BinaryReader(fs)
                Dim objHeader As clsHeader = New clsHeader

                ' wavファイルのヘッダー情報を読み込む
                objHeader.readHeader(fs, br)

                ' dataチャンクの読み込み
                intDataSize = BitConverter.ToInt32(br.ReadBytes(4), 0)

                ' 波形を格納
                Dim waveData As Byte() = br.ReadBytes(intDataSize)

                convertWaveData(objHeader, waveData)

            End Using

        Catch ex As Exception
            MessageBox.Show("Form1_Load:" & ex.ToString())
        End Try

    End Sub

    ''' 
    ''' 波形ファイルを作成する
    ''' 
    ''' 
    ''' 
    Sub convertWaveData(ByRef objHeader As clsHeader, ByRef waveData As Byte())

        Try
            ' 音声データの取得
            Dim intWork As Integer

            intWork = CInt((intDataSize / objHeader.shChannel) / (objHeader.shBitPerSample / 8))
            Dim valuesR(intWork) As Integer

            intWork = CInt((intDataSize / objHeader.shChannel) / (objHeader.shBitPerSample / 8))
            Dim valuesL(intWork) As Integer

            ' 1標本分の値を取得
            Dim frameIndex As Integer = 0
            Dim chanelIndex As Integer = 0

            Dim data(2) As Byte

            For i As Integer = 0 To (intDataSize / (objHeader.shBitPerSample / 8)) - 1

                Dim work As Integer = 0

                Select Case (objHeader.shBitPerSample)
                    Case 8
                        work = CInt(waveData(frameIndex))
                        frameIndex = frameIndex + 1

                    Case 16
                        Array.Copy(waveData, frameIndex, data, 0, 2)
                        work = CInt(BitConverter.ToInt16(data, 0))
                        frameIndex = frameIndex + 2

                    Case Else
                        MessageBox.Show("波形解析できません", "エラー",
                        MessageBoxButtons.OK, MessageBoxIcon.Error)

                End Select

                If (objHeader.shChannel = 1) Then
                    valuesR(i) = work
                Else
                    If (chanelIndex = 0) Then
                        chanelIndex = 1
                        valuesR(i / 2) = work
                    Else
                        chanelIndex = 0
                        valuesL(i / 2) = work
                    End If
                End If

            Next

            ' 波形描画
            Call makeChart(valuesR, objHeader.PlayTime)

        Catch ex As Exception
            MessageBox.Show("convertWaveData:" & ex.ToString())
        End Try

    End Sub

    ''' 
    ''' 波形描画
    ''' 
    ''' 
    ''' 横軸の数値(単位:秒)
    Private Sub makeChart(ByVal valuesR() As Integer, ByVal intPlayTime As Integer)

        Try

            With Chart1

                ' 初期化
                .Series.Clear()
                .Titles.Clear()

                ' 作成
                Dim objTitle As Title = New Title("波形ファイル")
                .Titles.Add(objTitle)

                Dim objSeries As Series = New Series
                objSeries.ChartType = SeriesChartType.Line ' グラフタイプ
                objSeries.Name = "Waveデータ"              ' 凡例に表示される文字列を指定

                For i As Integer = 0 To valuesR.Length - 1
                    objSeries.Points.AddXY(i, valuesR(i)) ' データ追加
                Next
                .Series.Add(objSeries)

                .BackColor = Color.White ' 背景色
                .Series(0).Color = Color.Lime ' グラフの色

                .ChartAreas(0).AxisX.MajorGrid.Enabled = True
                .ChartAreas(0).AxisX.MajorGrid.LineDashStyle = ChartDashStyle.Dot
                .ChartAreas(0).AxisX.MajorGrid.LineColor = Color.Green
                .ChartAreas(0).AxisX.MinorGrid.Enabled = False

                .ChartAreas(0).AxisY.MajorGrid.Enabled = True
                .ChartAreas(0).AxisY.MajorGrid.LineDashStyle = ChartDashStyle.Dot
                .ChartAreas(0).AxisY.MajorGrid.LineColor = Color.Green
                .ChartAreas(0).AxisY.MinorGrid.Enabled = False

            End With

        Catch ex As Exception
            MessageBox.Show("makeChart:" & ex.ToString())
        End Try

    End Sub

End Class

音ファイルを取り込んで画面に波形グラフを表示する

後は、実行ボタンを押せば、以下の画像が画面に表示されます。



 
なお、音ファイル(199msec)をプログラムに読み込ませると以下のグラフが描画されます。
 

うまく表示できましたでしょうか? もし以下でお困りでしたら

  • 集音機からくるデータをリアルタイムで処理したい
  • 画像描画までが遅すぎるのでもっと早くしたい
  • mp3、aviファイルなどの拡張子も対応したい
  • なぜかプログラムが落ちてしまう
  • コーデックの部分がよく解らない
  • ステレオ時は2つの波形を表示したい
  • FFT分析した結果も画面に表示したい
  • バンドパスフィルタをかけた結果を画面に表示したい

などお困り事があれば計測機器ソフト屋である弊社にお任せ下さい。
なお、上記プログラムにてご指摘などがある際は、お問合せフォームよりご連絡下さると大変助かります。

お問い合わせはこちら