wavファイルの読み込みと音源バッファの作成
第一回の内容では、alutCreateBufferFromFileを用いてwavファイルからOpenAL用の音源バッファを作成しました。今回は自前の関数でwavファイルを読み込み、alGenBufferとalBufferDataを用いて手動でバッファを作ることを目指します。なぜ、こんなことをするのかと言うと、次回行うストリーミング再生の為です。alutCreateBufferFromFile関数は非常に便利ですが、一度に全ての波形データを読み込んでしまいます。これでは非常に大きな音源ファイルを開く場合に大量のメモリが必要となります。mp3やoggといった圧縮ファイルも、再生時には解凍(実際には解凍ではないが)されたデータになる必要があり、10倍程度のメモリを必要とします。そのためにストリーミング再生という技術があり、メモリを最小に押さえることが出来ます。単純にいうと「サイズの大きい音源ファイルを少しずつ読み込みながら再生する」ことを指す。この少しずつ後から読み込むということをするためには、自作の読み込み関数が必要になります。今回はストリーミング再生に備えてwavファイルの自前読み込みにチャレンジして見ます。

まずは、読み込むwavファイルの構造を調べましょう。 それをここで解説をするかは、、、私も今から調べて作るので気分次第だな。

調べてわかったことが次の二点

1)OpenALのバッファに収める波形データ形式はPCM形式。
2)wavファイルの最も単純な波形データフォーマットがPCM形式。

ということで、今回は自作関数でwavファイルを読み込み、alGenBuffer()関数とalBufferDataでOpenAL 用バッファを作って再生するところまでとする。ストリーミング処理は次回に。wavの対応はPCM形式のみとする。
自作のwav読み込み関数。
wavファイルはヘッダー、フォーマット情報、波形データ部分と、チャンク形式で分かれています。ストリーミング再生する場合は、バッファ作成に必要な情報だけを前もって取得し、波形データは後から読み出すことになります。ここでは、それに備えて「必要な情報を取得し、波形データの先頭にファイルポインタをセットする(頭出し)」関数を作ります。 ソースは以下のようになりました。

int ReadHeaderWav(FILE* fp, int *channel, int* bit, int *size, int* freq){
	short res16;
	int res32;
	int dataSize, chunkSize;
	short channelCnt,bitParSample,blockSize;
	int samplingRate,byteParSec;
	
	int dataPos;
	int flag = 0;
	
	fread(&res32, 4, 1,fp);
	if(res32 != 0x46464952){	//"RIFF"
		return 1;	//error 1
	}

	//データサイズ = ファイルサイズ - 8 byte の取得
	fread(&dataSize, 4, 1, fp);
	
	//WAVEヘッダーの読み
	fread(&res32, 4, 1, fp);
	if(res32 != 0x45564157){	//"WAVE"
		return 2;	//error 2
	}

	while(flag != 3){
		//チャンクの読み
		fread(&res32, 4, 1, fp);
		fread(&chunkSize, 4, 1, fp);
		
		switch(res32){
		case 0x20746d66:	//"fmt "
			//format 読み込み
			//PCM種類の取得
			fread(&res16, 2, 1, fp);
			if(res16 != 1){
				//非対応フォーマット
				return 4;
			}
			//モノラル(1)orステレオ(2)
			fread(&channelCnt, 2, 1, fp);
			if(res16 > 2){
				//チャンネル数間違い
				return 5;
			}
			//サンプリングレート
			fread(&samplingRate, 4, 1, fp);
			//データ速度(byte/sec)=サンプリングレート*ブロックサイズ
			fread(&byteParSec, 4, 1, fp);
			//ブロックサイズ(byte/sample)=チャンネル数*サンプルあたりのバイト数
			fread(&blockSize, 2, 1, fp);
			//サンプルあたりのbit数(bit/sample):8 or 16
			fread(&bitParSample, 2, 1, fp);
			
			*channel = (int)channelCnt;
			*bit = (int)bitParSample;
			*freq = samplingRate;

			flag += 1;
				
			break;
		case  0x61746164:	//"data"

			*size = chunkSize;
			
			dataPos = ftell(fp);

			flag += 2;
			break;
		}

	}
	
	//頭出し("fmt "が"data"より後にあった場合のみ動く)
	if (dataPos != ftell(fp)){
		fseek(fp,dataPos,SEEK_SET);
	}

	return 0;
}

やっている事は単純で、ファイルの先頭からwavの形式にあわせて順番に情報を読み取り、 波形データの直前で読み込みを辞めるだけです。引数のfpはバイナリリード"rb"で開いたファイルである必要がありますね。

上記の関数を使ってファイルを読み込み再生するmain関数を作りましょう。 まず最初にfopen()でwavファイルを開きます。このときのOpen形式は"rb"とバイナリにしておきましょう。
続いて上で作った関数でwavの情報取得と頭出しを行います。

int ReadHeaderWav(FILE* fp, int *channel, int* bit, int *size, int* freq)

fpは先に開いたファイルのファイルポインタです。channelには読み込んだwavファイルのチャンネル数が返ります。モノラルなら1、ステレオなら2となります。bitには1サンプリングあたりを表す波形データのbit数となります。8もしくは16が格納されます。ただし、ステレオなら実際にはその倍のデータ領域が使われています。例えばステレオで16bitなら 2 * 2 = 4 byte/sampling となっています。 sizeには波形データ部分のバイト数が返ります。freqにはサンプリング周波数が収められます。44.1kHzのファイルでは44100となります。

続いて、波形データをメモリに読み込みましょう。今回はストリーミングではなく一括で読み込むこととします。単純に上で得られた波形データサイズの分だけfread関数で読み込んでください。

それではいよいよOpenALのバッファを作成してみます。 まずは

void alGenBuffers( ALsizei n, ALuint *buffers );

関数でバッファを作成します。nは作りたいバッファの数です。今回はバッファを一つ作ればいいので、1としましょう。buffersには作られたバッファの名前(ID)が格納されます。複数作る場合用に配列となっていますね。バッファをポインタでなく名前(ID)で管理するあたりは非常にOpenGLに似ていますね。

さらに作ったバッファにwavファイルから読み込んだ波形データを登録しましょう。

void alBufferData( ALuint buffer, ALenum format, const ALvoid *data,
                   ALsizei size, ALsizei freq );

bufferにはalGenBuffers()で作ったバッファの名前を指定します。formatにはチャンネル数と1サンプリングあたりのbit数から決まる定数を指定します。ReadHeaderWav()の戻り値を参考に次の4種類から選びます。

AL_FORMAT_MONO8
AL_FORMAT_MONO16
AL_FORMAT_STEREO8
AL_FORMAT_STEREO16

dataにはメモリ上に読み込んだ波形データのポインタを、sizeには波形データのバイト数を、freqにはサンプリング周波数を渡しましょう。これで、バッファの作成は終わりです。
この時点でメモリ上の波形データをクリアしても良いのかはよく調べてません。。。

あとは前回と同様にソースに登録して再生するだけです。 最終的にmain部分のコードは次のようになりました。

int main (int argc, char **argv)
{
	
	ALuint buffer, source;
	FILE *fp;
	int wavChannel, wavBit, wavSize, wavFreq;
	unsigned char *data;
	
	alutInit (&argc, argv);
	
	//wavの自作ロード
	fp = fopen("media\\file1.wav","rb");
	
	if (ReadHeaderWav(fp, &wavChannel, &wavBit, &wavSize, &wavFreq)){
		printf("非対応format\n");
		return 1;
	}
	
	
	//バッファの作成
	alGenBuffers(1, &buffer);

	//データ読み込み
	data = new unsigned char[wavSize];
	fread(data,wavSize,1,fp);
		
	//バッファへのデータ登録
	if(wavChannel==1){
		if(wavBit==8){
			alBufferData(buffer, AL_FORMAT_MONO8, data, wavSize, wavFreq);
		}else{
			alBufferData(buffer, AL_FORMAT_MONO16, data, wavSize, wavFreq);
		}
	}else{
		if(wavBit==8){
			alBufferData(buffer, AL_FORMAT_STEREO8, data, wavSize, wavFreq);
		}else{
			alBufferData(buffer, AL_FORMAT_STEREO16, data, wavSize, wavFreq);
		}
	}
	
	
	alGenSources (1, &source);
	alSourcei (source, AL_BUFFER, buffer);
	alSourcePlay (source);
	
	alutSleep (1);
	getch();

	
	alDeleteBuffers(1, &buffer);
	alDeleteSources(1, &source);
	
	delete [] data;
	fclose(fp);

	alutExit ();
	return EXIT_SUCCESS;
}




Atsushi M. Ito web