2002年8月2日金曜日:1.3.6.後始末を修正し、ファイルを置き換えた。

1.OggVorbis1 : OggVorbisをWAVEに変換する


1.1.OggVorbisとは

 現在アルゴリズムに対する特許が問題となっています。最初は特許使用料を取らずに、ある程度普及したところで突如として特許使用料を請求するのです。これによりGIF、MP3が使いにくくなり、また最近ではJPEGまで特許使用料を要求するようになりました。
 MP3の代替フォーマットとして現在有力なのがOggVorbisです。MP3よりも音質・圧縮率で優れていると言われ、一切特許に引っかかる技術を使っていないらしいです。OggVorbisは配付されているライブラリを使用する事により簡単に使用できますが、ドキュメントが全て英語であるため面倒だと思う人が結構多いのでは無いでしょうか? そこでOggVorbisからWAVEへの変換方法を説明します。

 なおここでの想定する使用方法は、OggVorbisからWAVEへ変換し、それをDirectAudioを使用して再生するものです。現在ゲームのBGMではMIDIかMP3が使われる事が多いですが、これからはMP3ではなくOggVorbisが使われる事を祈ります。

1.2.必要なファイルの入手と設定

 まずはhttp://www.vorbis.com/download_win.pspからOggdropXPdとOgg Vorbis Win32 SDKをダウンロードし、適当なフォルダに展開します。OggdropXPdはエンコーダ・デコーダで、Ogg Vorbis Win32 SDKが開発に必要なSDKです。
 OggdropXPdは起動したらWAVEファイルをD&Dするだけなので説明はいらないでしょう。

 Win32SDKはlibフォルダとincludeフォルダへパスを通します。VC++6.0の場合はメニューからツール>オプション>ディレクトリを選択します(fig1)。表示するディレクトリでインクルードファイルを指定して、Win32SDKのincludeフォルダを追加します。次にライブラリファイルを選択してWin32SDKのlibフォルダを使いします。

ツール>オプション>ディレクトリ
fig1:ツール>オプション>ディレクトリ

 これで設定は終わりです。次の節からコーディングに入りますが、開発ソフトはVC++6.0を想定しています。ですからそれ以外のソフトについては各自何とかしてください。

1.3.コーディング

 コーディングの説明に入る前にoggvorbis1.lzhをダウンロードして、この中に入っているoggvorbis.cppを見ながら説明を読んでください。

1.3.1.使用するヘッダとライブラリ

 list1の様にvorbis/codec.hとvorbis/vorbisfile.hをインクルードします。

list1:ヘッダのインクルード

#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>

 次にvorbis_static.lib、ogg_static.lib、vorbisfile_static.libをリンクします。デバッグバージョンではvorbis_static_d.lib、ogg_static_d.lib、vorbisfile_static_d.lib、DLLを使用するときはvorbis.lib、ogg.lib、vorbisfile.lib、DLLを使用するデバッグバージョンではvorbis_d.lib、ogg_d.lib、vorbisfile_d.libを使用してください。
 また、スタティックライブラリを使用するリリースバージョンでは/nodefaultlib:"LIBCMT"、デバッグバージョンでは/nodefaultlib:"LIBCMTD" /nodefaultlib:"LIBCD"というオプションを付けてください。

1.3.2.OggVorbis_File構造体の初期化と詳細収得

 OggVorbis_File構造体の初期化にはov_open関数を使用します。第1引数にはファイルポインタ、第2引数にはOggVorbis_File構造体へのポインタ、第3引数にはNULL、第4引数には0を指定してください。
 初期化が終わったら、ov_info関数を使用してOggVorbisファイルの詳細を収得します。第1引数に初期化したOggVorbis_File構造体へのポインタ、第2引数に-1を指定してください。戻り値がOggVorbisファイルの詳細を示すvorbis_info構造体です。

 初期化と詳細設定についてlist2を参照してください。file_nameはOggVorbisファイルの名前です。

list2:OggVorbis_File構造体の初期化と詳細収得

  FILE *fp;

  /* ファイルを開く */
  fp = fopen(file_name, "rb");
  if(fp == NULL){
    fprintf(stderr, "Open Error : %s", file_name);
    return -1;
  }

  OggVorbis_File vf;
  vorbis_info *vi;

  /* OggVorbis初期化 */
  if(ov_open(fp, &vf, NULL, 0) < 0) {
      fprintf(stderr,"Input does not appear to be an Ogg bitstream.\n");
      fclose(fp);
      return -1;
  }

  /* 詳細収得 */
  vi = ov_info(&vf,-1);
  if(vi == NULL){
    fprintf(stderr, "Get Info Error\n");
    return -1;
  }


1.3.3.メモリ確保

 WAVEにデコードしたデータを格納できるだけのメモリを確保します。まず、ヘッダはlist3のものを使用します。WAVEFORMATEXはwindows.hで宣言されています。

list3:WAVEヘッダ

/* WAVEファイルのヘッダ */
typedef struct{
  char ckidRIFF[4];
  int ckSizeRIFF;
  char fccType[4];
  char ckidFmt[4];
  int ckSizeFmt;
  WAVEFORMATEX WaveFmt;
  char ckidData[4];
  int ckSizeData;
} WAVEFILEHEADER;

 list4の様にしてヘッダサイズを求めます。sizeof(WAVEFILEHEADER)とすると、全てのメンバの合計サイズよりも大きくなってしまい、不具合が発生するので注意してください。

list4:WAVEヘッダのサイズ

  WAVEFILEHEADER wh;
  long whsize;

  /* ヘッダサイズの収得 */
  /* sizeof(wh) だと全てのメンバのサイズの合計より大きくなってしまう */
  whsize = sizeof(wh.ckidRIFF) + sizeof(wh.ckSizeRIFF) + sizeof(wh.fccType) +
           sizeof(wh.ckidFmt)  + sizeof(wh.ckSizeFmt)  + sizeof(wh.WaveFmt) +
           sizeof(wh.ckidData) + sizeof(wh.ckSizeData);

 次にデータ本体のサイズを求め、ヘッダサイズとの合計分だけメモリを確保します。list5を参照してください。oggはchar型のポインタへのポインタで、wordは量子化バイト数です。

list5:メモリ確保

  long data_size;

  /* デコード後のデータサイズを求め、メモリ確保 */
  data_size = (long)ceil(vi->channels * vi->rate * ov_time_total(&vf,-1) * word);
  *ogg = (char *)malloc(data_size + whsize);
  if(ogg == NULL){
    free(ogg);
    ov_clear(&vf);
    fclose(fp);
    fprintf(stderr, "malloc Error\n");
    return -1;
  }


1.3.4.デコード

 OggVorbisからWAVEに変換(デコード)します。デコードにはov_read関数を使用します。第1引数にOggVorbis_File構造体へのポインタ、第2引数にバッファへのポインタ、第3引数にバッファサイズ、第4引数に0、第5引数に量子化バイト数、第6引数に1、第7引数にint型変数へのポインタを指定してください。戻り値がバッファに書き込まれたバイト数になります。これが0になるまでループを回します。
 ov_read関数は一回でバッファ一杯まで書き込むとは限らないという事に注意してください。
 list6を参照してください。

list6:デコード

  long size = 0;
  long ret;
	
  /* デコード */
  /* 一回でバッファ全てにデコードされるとは限らない */
  /* 一回につき 256〜4096Byte デコードされるようだ  */
  while(!eof){
    ret = ov_read(&vf, *ogg + size + whsize, data_size - size, 0, word, 1, &current_section);
    if (ret == 0) {
      eof=1;
    } else if (ret < 0) {
      free(ogg);
      ov_clear(&vf);
      fclose(fp);
      fprintf(stderr, "Decode Error\n");
      return -1;
    } else {
      size += ret;
    }
  }


1.3.5.ヘッダ

 WAVEヘッダを初期化し、書き込みます。書き込む際はWAVEFILEHEADER構造体を全て書き込むと意図しないデータも書き込まれてしまうので注意してください。
 その他には特に説明する事もないのでlist7を参照してください。

list7:ヘッダ

  /* ヘッダの初期化 */
  memcpy(wh.ckidRIFF, "RIFF", 4);
  wh.ckSizeRIFF = whsize + size - 8;
  memcpy(wh.fccType, "WAVE", 4);
  memcpy(wh.ckidFmt, "fmt ", 4);
  wh.ckSizeFmt = sizeof(WAVEFORMATEX);

  wh.WaveFmt.cbSize          = sizeof(WAVEFORMATEX);
  wh.WaveFmt.wFormatTag      = WAVE_FORMAT_PCM;
  wh.WaveFmt.nChannels       = vi->channels;
  wh.WaveFmt.nSamplesPerSec  = vi->rate;
  wh.WaveFmt.nAvgBytesPerSec = vi->rate * vi->channels * word;
  wh.WaveFmt.nBlockAlign     = vi->channels * word;
  wh.WaveFmt.wBitsPerSample  = word * 8;

  memcpy(wh.ckidData, "data", 4);
  wh.ckSizeData = size;


  /* メモリへのヘッダの書き込み */
  int s = 0;
  memcpy(*ogg, &wh.ckidRIFF, sizeof(wh.ckidRIFF));          s += sizeof(wh.ckidRIFF);
  memcpy(*ogg + s, &wh.ckSizeRIFF, sizeof(wh.ckSizeRIFF));  s += sizeof(wh.ckSizeRIFF);
  memcpy(*ogg + s, &wh.fccType, sizeof(wh.fccType));        s += sizeof(wh.fccType);
  memcpy(*ogg + s, &wh.ckidFmt, sizeof(wh.ckidFmt));        s += sizeof(wh.ckidFmt);
  memcpy(*ogg + s, &wh.ckSizeFmt, sizeof(wh.ckSizeFmt));    s += sizeof(wh.ckSizeFmt);
  memcpy(*ogg + s, &wh.WaveFmt, sizeof(wh.WaveFmt));        s += sizeof(wh.WaveFmt);
  memcpy(*ogg + s, &wh.ckidData, sizeof(wh.ckidData));      s += sizeof(wh.ckidData);
  memcpy(*ogg + s, &wh.ckSizeData, sizeof(wh.ckSizeData));


1.3.6.後始末

 全ての作業が終わったらov_clear関数で後始末をします。fcloseでファイルを閉じてはいけません。

list8:後始末

  /* cleanup */
  ov_clear(&vf);


1.3.7.煮るなり焼くなりお好きにどうぞ

 ここまでの作業でWAVEデータが得られます。後はファイルに書き込むなり、WAVEとして再生するなりしてください。
 DirectAudioでの使用例をoggvorbis2.lzhに置いておきますので参考にしてください。変数名が変だったり、少々読みにくかったりしますが勘弁してください。こんなのじゃあさっぱり解らないという方は、言ってくれれば修正するかもしれません。



[index]

テレワークならECナビ Yahoo 楽天 LINEがデータ消費ゼロで月額500円〜!
無料ホームページ 無料のクレジットカード 海外格安航空券 海外旅行保険が無料! 海外ホテル