音楽ファイルと使用されるWaveファイルを解析しました。Visual Studio Express 2013を使用してC#言語によりWave情報から振幅値データを取得しました。
Waveファイルフォーマットの仕様
Waveファイルフォーマットの仕様を次に示します。
| 名称 | サイズ | 機能 | 
|---|---|---|
| RIFFヘッダ | 4バイト | RIFFファイル形式のファイルであることを表す文字列。 固定文字列「RIFF」  | 
| ファイルサイズ | 4バイト | ファイルサイズ(バイト)。リトルエンディアン | 
| WAVEヘッダ | 4バイト | RIFF形式のWAVEファイルであることを表す文字列。 固定文字列「WAVE」  | 
| フォーマットチャンクヘッダ | 4バイト | 以下が”fmt ”(フォーマット)チャンクの定義を表す | 
| フォーマット定義サイズ | 4バイト | フォーマットの定義関連のデータサイズ | 
| フォーマットID | 2バイト | 圧縮のフォーマットID 0x0001:リニアPCM 0x0002:MS ADPCM 0x0005:IBM CSVD 0x0006:A-Law 0x0007:μ-Law  | 
| チャンネル数 | 2バイト | モノラルの場合:0x0001 ステレオの場合:0x0002  | 
| サンプリングレート | 4バイト | 単位:Hz | 
| データ転送速度[byte/sec] | 4バイト | 44.1kHz 16bitステレオの場合:44100×2×2 =176400 44.1KHz 16bitモノラルの場合:44100×2×1=88200  | 
| バイト/サンプル | 2バイト | サンプルあたりのバイト数 16bitステレオ:4 16bitモノラル:2 8bitモノラル:1  | 
| 量子化ビット数 | 2バイト | 16/8 | 
| データチャンクヘッダ | 4バイト | 以下が”data”(データ)チャンクの定義を表す | 
| データ定義サイズ | 4バイト | データの定義関連のデータサイズ | 
| サンプリングデータ | nバイト | 音楽サンプルデータ。リトルエンディアン | 
アドレス「0」から4バイトに「RIFF」が見えます。アドレス「c」から4バイトに「fmt」チャンクが見えます。アドレス「24」から4バイトに「data」が見えます。
アドレス「59c2c」から4バイトに「LIST」チャンクが見えます。
様々なフォーマットを持つWaveファイルのサンプルが「WAV」にあります。「RIFF Inspector」を利用して表示させると、次のように分かりやすい形式で表示できます。
Waveデータの振幅値データの取得プログラムの作成
Waveファイルフォーマットの仕様に基づいて、C#で作成したWaveデータの振幅値データの取得メソッド「ReadWave」を次に示します。
private bool ReadWave(string waveFilePath)
{
    //Debug.WriteLine("ReadWave : " + waveFilePath);
    maxValue = 0;
   // ファイルの存在を確認する
    if (!File.Exists(waveFilePath))
    {
        return false;
    }
    using (FileStream fs = new FileStream(waveFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
    {
        try
        {
            BinaryReader br = new BinaryReader(fs);
            waveHeader.RiffHeader = Encoding.GetEncoding(20127).GetString(br.ReadBytes(4));
            waveHeader.FileSize = BitConverter.ToInt32(br.ReadBytes(4), 0);
            waveHeader.WaveHeader = Encoding.GetEncoding(20127).GetString(br.ReadBytes(4));
            bool readFmtChunk = false;
            bool readDataChunk = false;
            while (!readFmtChunk || !readDataChunk)
            {
                // ChunkIDを取得する
                string chunk = Encoding.GetEncoding(20127).GetString(br.ReadBytes(4));
                if (chunk.ToLower().CompareTo("fmt ") == 0)
                {
                    //    Debug.WriteLine("fmt : " + waveFilePath);
                    // fmtチャンクの読み込み
                    waveHeader.FormatChunk = chunk;
                    waveHeader.FormatChunkSize = BitConverter.ToInt32(br.ReadBytes(4), 0);
                    waveHeader.FormatID = BitConverter.ToInt16(br.ReadBytes(2), 0);
                    waveHeader.Channel = BitConverter.ToInt16(br.ReadBytes(2), 0);
                    waveHeader.SampleRate = BitConverter.ToInt32(br.ReadBytes(4), 0);
                    waveHeader.BytePerSec = BitConverter.ToInt32(br.ReadBytes(4), 0);
                    waveHeader.BlockSize = BitConverter.ToInt16(br.ReadBytes(2), 0);
                    waveHeader.BitPerSample = BitConverter.ToInt16(br.ReadBytes(2), 0);
                    readFmtChunk = true;
                }
                else if (chunk.ToLower().CompareTo("data") == 0)
                {
                    //   Debug.WriteLine("data : ");
                    // dataチャンクの読み込み
                    waveHeader.DataChunk = chunk;
                    waveHeader.DataChunkSize = BitConverter.ToInt32(br.ReadBytes(4), 0);
                    waveData = br.ReadBytes(waveHeader.DataChunkSize);
                    //  Debug.WriteLine(string.Format("waveData : {0:X} {1:X}", waveData[0], waveData[1]));
                    // 再生時間を算出する
                    int bytesPerSec = waveHeader.SampleRate  * waveHeader.BlockSize;
                    waveHeader.PlayTimeMsec = (int)(((double)waveHeader.DataChunkSize / (double)bytesPerSec) * 1000);
                    Debug.WriteLine(string.Format("データサイズ:{0} 再生時間 : {1}", waveHeader.DataChunkSize, waveHeader.PlayTimeMsec));
                    tbxPlay.Text = waveHeader.PlayTimeMsec.ToString() + "ms秒";
                    convertWaveData();
                    readDataChunk = true;
                }
                else
                {
                    Debug.WriteLine("chunk : " + chunk);
                    // 不要なチャンクの読み捨て
                    Int32 size = BitConverter.ToInt32(br.ReadBytes(4), 0);
                    if (0 < size)
                    {
                        br.ReadBytes(size);
                    }
                }
            }
        }
        catch
        {
            fs.Close();
            return false;
        }
    }
    return true;
}
- 27行目でチャンクを呼び出し、29行目でフォーマット、44行目でデータのそれぞれチャンクを判断しています。
 - 33行目から40行目までで。fmtチャンクに含まれるフォーマットID等を各メソッドに保存しています。
 - 51行目でdataチャンクに保存されているサンプリングデータをバイト単位で保存し、55行目から56行目で再生時間を算出しています。
 - 59行目のメソッド「convertWaveData」で取得したサンプリングデータを符号付きint型に変換します。
 
符号付きint型変換メソッド「convertWaveData」は、チャンネル数と量子化ビット数に従って、取得したサンプリングデータを符号付きint型に変換します。8ビット/16ビットのモノラルとステレオのWeveファイルに対応しています。
private void convertWaveData()
{
    try
    {
        // 音声データの取得
        valuesR = new int[(waveHeader.DataChunkSize / waveHeader.Channel) / (waveHeader.BitPerSample / 8)];
        valuesL = new int[(waveHeader.DataChunkSize / waveHeader.Channel) / (waveHeader.BitPerSample / 8)];
        Debug.WriteLine(string.Format("valuesR.Length : {0} ", valuesR.Length));
        // 1標本分の値を取得
        int frameIndex = 0;
        int chanelIndex = 0;
        for (int i = 0; i < waveHeader.DataChunkSize / (waveHeader.BitPerSample / 8); i++)
        {
            byte[] data = new byte[2];
            int work = 0;
            switch (waveHeader.BitPerSample)
            {
                case 8:
                    work = (int)waveData[frameIndex];
                    frameIndex += 1;
                    break;
                case 16:
                    Array.Copy(waveData, frameIndex, data, 0, 2);
                    work = (int)BitConverter.ToInt16(data, 0);
                    frameIndex += 2;
                    break;
                default:
                    MessageBox.Show("波形解析できません", "エラー",
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                    break;
            }
            if (waveHeader.Channel == 1)
            {
                valuesR[i] = work;
            }
            else
            {
                if (chanelIndex == 0)
                {
                    chanelIndex = 1;
                    valuesR[i/2] = work;
                    Debug.WriteLine(string.Format("valuesR :{0} {1:X} ", i,valuesR[i/2]));
                }
                else
                {
                    chanelIndex = 0;
                    valuesL[i/2] = work;
                    //    Debug.WriteLine(string.Format("valuesL : {0:X}", valuesL[i/2]));
                }
            }
        }
        Debug.WriteLine(string.Format("maxValue : {0}", maxValue));
    }
    catch (Exception e)
    {
        Debug.WriteLine(e.ToString());
    }
}
- 6行目と7行目でそれぞれR/Lの音声データの保存領域を確保します。
 - 19行目で量子化ビット数に従い、1バイトのデータか2バイトのデータかを判断します。
 - 35行目でチャンネル数がモノラルの場合はR側のバッファに、ステレオの場合は、R/Lのバッファにそれぞれ保存します。
 
Waveデータの振幅値データの取得プログラムの実行
実際の16ビットステレオのWaveファイルの「fmt 」チャンクをダンプした結果を次に示します。
RIFF ヘッダ: RIFF ファイルサイズ: 31828 WAVE ヘッダ: WAVE フォーマットチャンク: fmt フォーマットチャンクサイズ: 16 フォーマット ID: 1 チャンネル数: 2 サンプリングレート: 44100 1秒あたりのデータ数: 176400 ブロックサイズ: 4 1サンプルあたりのビット数: 16
16ビットステレオのWaveファイルの取得したサンプリングデータを符号付きint型に変換します。
valuesR : FFFFFF93 valuesR : FFFFFFAE valuesR : FFFFFFB8 valuesR : FFFFFFB7 valuesR : FFFFFFAF valuesR : FFFFFFAD valuesR : FFFFFFBE valuesR : FFFFFFD3 valuesR : FFFFFFCA valuesR : FFFFFFB7 valuesR : FFFFFFAE valuesR : FFFFFFB0 ・・・ ・・・ valuesR : FFFFFFD5 valuesR : FFFFFFE7
											
				

