WAVE ファイル


波形ファイルフォーマット

これまでは、メモリ上のバッファに波形データを生成したり
あるいは、入力デバイスからバッファに音声を記録するということを行ってきました
ここでは、さらに音声データをディスクファイルに保存する方法を解説します

PCM 音声データは、一般に拡張し *.wav として保存される
WAVE ファイルフォーマットとして、ディスクに保存することができます

WAVE や動画ファイルなどのマルチメディアデータは
通常、RIFF(Resource Interchange File Format) という形式に属します
RIFF はマルチメディアデータの保存と交換を行う一般的な手段を提供していて
Windows は RIFF のためのマルチメディアファイル入出力 API も提供しています(後記)

RIFF 形式は4文字の ASCII コードでファイルの要素を指定し
4文字以下の場合は、余った分だけ右詰めでスペース文字を埋め込みます
その後にデータ部分のサイズをバイト単位で示した DWORD が続き
その次に、実際のデータ等が格納されています

これら、4文字のコードと情報、データを合わせてチャンクと呼びます
最初の4文字コードは、チャンクタイプを示すコードなのです
チャンクのデータ部は、それぞれのチャンクタイプによって独自に定義されます
チャンクタイプによっては、データ部に他のチャンクを含むものもあります

この時、チャンクは親子関係にあると考えることができます
元のチャンクを「親チャンク」と呼び、子のチャンクを「サブチャンク」と呼びます
また、ひとつのチャンクに複数のファイルデータを含ませるというものも存在します

RIFF 形式のファイルは、必ず RIFF チャンクが最初に存在します
RIFF チャンクは、RIFF という文字に始まり、ファイルサイズ、フォームタイプと続きます
フォームタイプとは、ファイルのデータ形式を表す4文字の ASCII コードです
その後にサブチャンクが続くという形になります

アドレス +0+1+2+3+4+5+6+7+8+9+A+B+C+D+E+F
00000000 5249464656A8010057415645666D7420

これは、WAVE 形式のファイルの先頭16バイトをバイナリで表示したものです
16進数 52494646 という並びは、ASCII 文字で RIFF という文字になります

その次に、RIFF チャンクのサイズを引いた波形データのサイズを4バイトで表しています
0001A856 という値は 10 進数で 108630 バイトということになります
実際のファイルサイズは、これに 8 バイト追加した 108638 バイトとなります

8 バイト目からの DWORD はフォームタイプを表しています
57415645 という値は、ASCII コードの WAVE という文字になります
WAVE ファイルの RIFF は、必ず WAVE というフォームタイプを持っています

WAVE フォームタイプの RIFF は、"fmt " と "data" サブチャンクを持ちます
"fmt " チャンクは、3文字なので終端にスペース文字があることを忘れないで下さい

fmt チャンクは WAVE ファイルのフォーマットに関する情報を持ちます
データサイズを指定する4バイトには、フォーマットチャンクのサイズをバイト単位で指定します
データ部分には WAVEFORMATEX 構造体のサイズを指定します
PCM 音源であれば、WAVEFORMATEX 構造体の先頭16バイトで十分です

data チャンクは、波形データのサイズと波形データで構成されます
すなわち、WAVE 形式のファイルは、最低でも次のような構成になります

アドレス +0+1+2+3+4+5+6+7+8+9+A+B+C+D+E+F
00000000 524946462D2B000057415645666D7420
00000010 1000000001000200112B0000112B0000
00000020 0200080064617461112B000000920A00

赤色の部分が "RIFF" チャンクの始まり、緑色が "fmt " チャンクの始まり
そして、青色の部分が data チャンク、すなわち波形データの始まりを表しています
省略していますが、data チャンクのデータ部分として、波形データが続きます

この仕様を十分に理解すれば、低レベル API で作り出した波形データを
ディスクファイルとして直接保存することができるようになります
#include <windows.h>
#define SRATE 	11025

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	BYTE *bWave;
	DWORD dwCount , dwWave = 0 , dwWriteSize;
	const DWORD dwFileSize = SRATE + 36 ,
		dwFmtSize = 16 , dwWaveSize = SRATE;
	WAVEFORMATEX wfe;
	HANDLE hFile;

	hFile = CreateFile(lpCmdLine , GENERIC_WRITE , 0 , NULL ,
		CREATE_ALWAYS , FILE_ATTRIBUTE_NORMAL , NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		MessageBox(NULL , TEXT("ファイルが開けません") , NULL , MB_OK);
		return 1;
	}

	WriteFile(hFile , TEXT("RIFF") , 4 , &dwWriteSize , NULL);
	WriteFile(hFile , &dwFileSize , 4 , &dwWriteSize , NULL);
	WriteFile(hFile , TEXT("WAVE") , 4 , &dwWriteSize , NULL);

	bWave = malloc(SRATE);
	for (dwCount = 0 ; dwCount < SRATE ; dwCount++) {
		if (dwWave > 256) dwWave = 0;
		*(bWave + dwCount) = dwWave;
		dwWave += 10;
	}

	wfe.wFormatTag = WAVE_FORMAT_PCM;
	wfe.nChannels = 1;
	wfe.nSamplesPerSec = SRATE;
	wfe.nAvgBytesPerSec = SRATE;
	wfe.wBitsPerSample = 8;
	wfe.nBlockAlign = wfe.nChannels * wfe.wBitsPerSample / 8;

	WriteFile(hFile , TEXT("fmt ") , 4 , &dwWriteSize , NULL);
	WriteFile(hFile , &dwFmtSize , 4 , &dwWriteSize , NULL);
	WriteFile(hFile , &wfe , 16 , &dwWriteSize , NULL);
	WriteFile(hFile , TEXT("data") , 4 , &dwWriteSize , NULL);
	WriteFile(hFile , &dwWaveSize , 4 , &dwWriteSize , NULL);
	WriteFile(hFile , bWave , SRATE , &dwWriteSize , NULL);

	free(bWave);
	CloseHandle(hFile);
	return 0;
}
このプログラムは、コマンドライン引数で指定した名前のファイル名で
1秒間のノコギリ波を表す WAVE ファイルを生成します

ただし、RIFF ファイルの直接の読みこみはこれよりもずっと複雑になります
まず、チャンクは予想しないものが含まれる可能性があるのです
通常、アプリケーションが認知しないチャンクが存在する場合、それを無視します
WAVE ファイルにおいても、"fmt " と "data" 以外のチャンクが含まれることがよくあります
例えば、データファイルについての情報を提供する "INFO" チャンクが含まれることがあります

通常 RIFF 形式のマルチメディアファイルの入出力には
専用に用意された、マルチメディアファイル入出力 API を用いて操作します
これについては、後ほど詳しく説明します



前のページへ戻る次のページへ