音楽ファイルと使用される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