WAVEデータの読み込み
WAVEデータは、大抵RIFFという形式でファイルに収まっています。RIFFでは「チャンク」と呼ばれるまとまりでデータを管理し、各チャンクは「ASCIIコード4文字」「チャンクのサイズ」「データ」の並びで構成されています。
WAVEデータでは「fmt 」と「data」というチャンクがあります(fmtは四文字にするために後ろにスペースが入っています)。fmt(フォーマットチャンク)にはWAVEの再生に必要な情報が入っています。data(データチャンク)には音のデータが入っています。
要するに、これを解析してデータを取り出せばいいわけです。ただのファイル入力なので、C言語標準のFILE
で読み込んでも構いませんが、面倒なのでAPIに頼りましょう。
読み込みの流れは次のようになります。
- ファイルを開く
- WAVEかどうかチェック
- 「fmt 」チャンクに入る
- フォーマットデータを取得
- 「fmt 」チャンクから出る
- 「data」チャンクに入る
- データを取り出す
- 再生中は7.を繰り返す
- 「data」チャンクから出る
- データを読み込む
- ファイルを閉じる
具体的には次のようなコードになります。
//ファイルネーム static LPCTSTR openFileName = TEXT("i:\\temp\\test.wav"); //WAVEファイルを読み込むための構造体 static HMMIO hmmio; WAVEFORMATEX wfe; //WAVEデータ読み込みのための設定 //WAVEファイルのオープン hmmio = mmioOpen((LPSTR)openFileName,NULL,MMIO_READ); if(!hmmio){ MessageBox(0, "WAVEファイルのオープンに失敗","MyApp", MB_OK); return FALSE; } //WAVEファイルかのチェック if(mmioDescend(hmmio,&ckRiff, NULL, 0) != MMSYSERR_NOERROR || ckRiff.ckid != FOURCC_RIFF || ckRiff.fccType != mmioFOURCC('W', 'A', 'V', 'E')) { MessageBox(0, "WAVEファイルではありません","MyApp", MB_OK); mmioClose(hmmio, 0); return FALSE; } /********************************** フォーマットチャンクの読み込み **********************************/ ckRiff.ckid = mmioFOURCC('f', 'm', 't', ' '); //フォーマットチャンクに侵入 if(mmioDescend(hmmio, &ckRiff, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR){ MessageBox(0, "フォーマットチャンクの読み込みに失敗", "MyApp", MB_OK); mmioClose(hmmio, 0); return FALSE; } //フォーマットチャンクを読み込む ZeroMemory(&wfe, sizeof(wfe)); if(mmioRead(hmmio, (HPSTR)&wfe, (long)(ckRiff.cksize)) != (long)(ckRiff.cksize)) { MessageBox(0, "フォーマットチャンクの読み込みに失敗", "MyApp", MB_OK); mmioClose(hmmio, 0); return FALSE; } //フォーマットチャンクから抜け出す if(mmioAscend(hmmio, &ckRiff, 0) != MMSYSERR_NOERROR){ MessageBox(0, "フォーマットチャンクの読み込みに失敗", "MyApp", MB_OK); mmioClose(hmmio, 0); return FALSE; } /********************************** データチャンクの読み込み **********************************/ ckRiff.ckid = mmioFOURCC('d', 'a', 't', 'a'); //データチャンクに侵入 if(mmioDescend(hmmio, &ckRiff, NULL, MMIO_FINDCHUNK) != MMSYSERR_NOERROR){ MessageBox(0, "データチャンクの侵入に失敗","MyApp", MB_OK); mmioClose(hmmio, 0); return FALSE; } //16Bitステレオかをチェック if(wfe.nChannels !=2 || wfe.wBitsPerSample !=16){ MessageBox(0, "16Bitまたはステレオでないので再生できません", "MyApp", MB_OK); } //読み込み mmioRead(hmmio,(HPSTR)buf,bufferSize); //データチャンクから抜け出す mmioAscend(hmmio, &ckRiff, 0); mmioClose(hmmio,0);
いろいろと面倒なことをしていますが、すべて必要な処理です。
関数 | 説明 |
mmioOpen | ファイルを開いてhmmio を返します。 |
mmioFOURCC | FOURCC、つまりASCII4文字のチャンク名を作ります。 |
mmioDescend | チャンクに入ります。 |
mmioRead | データを読み込みます。 |
mmioSeek | 読み位置を変えます(後で使います)。 |
mmioAscend | チャンクから抜け出します。 |
mmioClose | hmmioOpen で開いたファイルを閉じます。 |
関数について詳しく説明すると大変なことになるので、これらの詳細は参考資料を確認してください。とりあえず、開き終わった後も使うことになる「mmioRead
」と「mmioSeek
」は覚えておいてください。
LONG mmioRead( HMMIO hmmio,//ハンドル HPSTR pch,//データを入れるバッファ LONG cch//読み込むサイズ );
LONG mmioSeek( HMMIO hmmio, //ハンドル LONG lOffset,//オフセット int iOrigin //どの位置から「lOffset」分ずらすかを決める。 ); //SEEK_CUR : 現在のファイル位置から lOffset バイトの位置を探します。 //SEEK_END : ファイルの終わりから lOffset バイトの位置を探します。 //SEEK_SET : ファイルの先頭から lOffset バイトの位置を探します。
再生の準備
再生は「waveOut~」という関数を使います。
関数 | 説明 |
waveOutOpen | デバイスを開く。 |
waveOutPrepareHeader | データブロックを初期化する。 |
waveOutWrite | デバイスにデータブロックを転送する。 |
waveOutUnprepareHeader | waveOutPrepareHeader で行われた初期化をクリーンアップする。 |
waveOutReset | 再生を停止する。 |
waveOutClose | デバイスを閉じる。 |
流れは次のようになります。
- デバイスを開く
- データを入れるバッファを準備
- データブロックを初期化
- データを書き込む
- 再生中は4.を繰り返す
- 再生を停止
- データブロックをクリーンアップ
- バッファを解放
- デバイスを閉じる
ソースコードは次のようになります。
//バッファ情報 static WAVEHDR whdr; //バッファ static short *wWave; //デバイスを開くための構造体 WAVEFORMATEX wfe; //デバイスへのハンドル static HWAVEOUT hWave; //再生周波数を設定する。WAVEファイルから取得することもできる int srate =48000; //周波数 const int channel =2; //チャンネル数 int bufferLenght = srate * channel; int bufferSize =bufferLenght * sizeof(short); //バッファのための領域確保 wWave = (short *)malloc(bufferSize); //出力デバイスの設定 wfe.nChannels = channel;//今回は2固定 wfe.wBitsPerSample = 16;//今回は16固定 //waveOutデバイスのための情報をセットする wfe.wFormatTag = WAVE_FORMAT_PCM; wfe.nSamplesPerSec = srate; wfe.nBlockAlign = wfe.nChannels * wfe.wBitsPerSample / 8; wfe.nAvgBytesPerSec = wfe.nSamplesPerSec * wfe.nBlockAlign; //出力デバイスを開く waveOutOpen(&hWave , WAVE_MAPPER , &wfe , (DWORD)hWndMain , 0 , CALLBACK_WINDOW); //再生バッファにデータを送るための設定 whdr.lpData = (LPSTR)wWave; whdr.dwBufferLength = bufferSize; whdr.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP; whdr.dwLoops = 1; //ロック waveOutPrepareHeader(hWave , &(whdr) , sizeof(WAVEHDR)); //再生バッファに書き込み whdr[bufferSelect].dwBufferLength =bufferSize; waveOutWrite((HWAVEOUT)wParam , &whdr , bufferSize); //再生をストップ waveOutReset((HWAVEOUT)wParam); //クリーンアップ waveOutUnprepareHeader((HWAVEOUT)wParam , &whdr , sizeof(WAVEHDR)); free(wWave); //クローズ waveOutClose(hWave);
「srate」はサンプリング周波数のことです。
上記のコードは、あくまでイメージです。ですが、実際のソースコードから取り出して再構成しているので、流れの説明にはなっていると思います。