ads by Amazon

2013年4月11日木曜日

10. wavファイルの構造と変換

効果音用のPCMデータは、wavファイルから取得することにします。

(1)wavファイルのフォーマット
↓のサイトに分かり易く記述されています。
http://www.kk.iij4u.or.jp/~kondo/wave/

(2)wavファイルはそのまま使ってはならない
wavファイル(RIFF構造の一種)は、非常に汎用性の高いフォーマットです。
汎用性が高いということは、逆説すれば無駄(冗長)な情報が多いということです。
例えば、周波数、ビットレート、チャネル数などの情報は、VGS音源の場合は22050Hz、16bit、1chに固定されているので、ファイルに持たせるのはナンセンスです。そこで、wavファイルから必要な情報を抜き取り、独自ファイル(PCMファイル)のフォーマットに変換したものをリソースとして持つことにします。
なお、VGS音源にとって必要な情報は、以下の2種類です。
  • パルス符号の数
  • パルス符号データの塊
(3)変換コマンド(vgswav)
VGSでは、22050Hz、16bit、1chのwavファイルを独自形式(PCM)に変換するvgswavというコマンドを提供しています。vgswavコマンドのソースコードは次のような感じになっています。

■vgswav.c
/* WAVEを独自形式のPCMデータに変換する */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* 情報ヘッダ */
struct DatHead {
    char riff[4];
    unsigned int fsize;
    char wave[4];
    char fmt[4];
    unsigned int bnum;
    unsigned short fid;
    unsigned short ch;
    unsigned int sample;
    unsigned int bps;
    unsigned short bsize;
    unsigned short bits;
    char data[4];
    unsigned int dsize;
};

int main(int argc,char* argv[])
{
    FILE* fpR=NULL;
    FILE* fpW=NULL;
    int rc=0;
    struct DatHead dh;
    char* data=NULL;
    char mh[4];

    /* 引数チェック */
    rc++;
    if(argc<3) {
        fprintf(stderr,"usage: vgswav input(wav) output(pcm)\n");
        goto ENDPROC;
    }

    /* 読み込みファイルをオープン */
    rc++;
    if(NULL==(fpR=fopen(argv[1],"rb"))) {
        fprintf(stderr,"ERROR: Could not open: %s\n",argv[1]);
        goto ENDPROC;
    }

    /* 情報ヘッダを読み込む */
    rc++;
    if(sizeof(dh)!=fread(&dh,1,sizeof(dh),fpR)) {
        fprintf(stderr,"ERROR: Invalid file header.\n");
        goto ENDPROC;
    }

    /* 形式チェック */
    rc++;
    if(0!=strncmp(dh.riff,"RIFF",4)) {
        fprintf(stderr,"ERROR: Not RIFF format.\n");
        goto ENDPROC;
    }
    rc++;
    if(0!=strncmp(dh.wave,"WAVE",4)) {
        fprintf(stderr,"ERROR: Not WAVE format.\n");
        goto ENDPROC;
    }
    rc++;
    if(0!=strncmp(dh.fmt,"fmt ",4)) {
        fprintf(stderr,"ERROR: Invalid format.\n");
        goto ENDPROC;
    }
    rc++;
    if(0!=strncmp(dh.data,"data",4)) {
        fprintf(stderr,"ERROR: Invalid data.\n");
        goto ENDPROC;
    }

    printf("Header of %s:\n",argv[1]);
    printf(" - Format: %d\n",dh.fid);
    printf(" - Channel: %dch\n",dh.ch);
    printf(" - Sample: %dHz\n",dh.sample);
    printf(" - Transform: %dbps\n",dh.bps);
    printf(" - Block-size: %dbyte\n",(int)dh.bsize);
    printf(" - Bit-rate: %dbit\n",(int)dh.bits);
    printf(" - PCM: %dbyte\n",(int)dh.dsize);


    rc++;
    if(22050!=dh.sample) {
        fprintf(stderr,"ERROR: Sampling rate is not 22050Hz.\n");
        goto ENDPROC;
    }
    rc++;
    if(1!=dh.ch) {
        fprintf(stderr,"ERROR: Sampling channel is not 1(mono).\n");
        goto ENDPROC;
    }
    rc++;
    if(16!=dh.bits) {
        fprintf(stderr,"ERROR: Sampling bit rate is not 16bit.\n");
        goto ENDPROC;
    }
    rc++;
    if(dh.sample*2!=dh.bps) {
        fprintf(stderr,"ERROR: Invalid transform-rate(byte/sec).\n");
        goto ENDPROC;
    }

    /* 波形データを読む込む領域を確保する */
    rc++;
    if(NULL==(data=(char*)malloc(dh.dsize))) {
        fprintf(stderr,"ERROR: Memory allocation error.\n");
        goto ENDPROC;
    }

    /* 波形データを読み込む */
    rc++;
    if(dh.dsize!=fread(data,1,dh.dsize,fpR)) {
        fprintf(stderr,"ERROR: Could not read PCM data.\n");
        goto ENDPROC;
    }

    /* 書き込みファイルをオープン */
    rc++;
    if(NULL==(fpW=fopen(argv[2],"wb"))) {
        fprintf(stderr,"ERROR: Could not open: %s\n",argv[2]);
        goto ENDPROC;
    }

    /* ヘッダ書き込み */
    rc++;
    strcpy(mh,"EFF");
    if(4!=fwrite(mh,1,4,fpW)) {
        fprintf(stderr,"ERROR: Could not write header.\n");
        goto ENDPROC;
    }

    /* サイズ情報書き込み(Big-endian) */
    rc++;
    mh[0]=((dh.dsize & 0xFF000000) >> 24) & 0xFF;
    mh[1]=((dh.dsize & 0x00FF0000) >> 16) & 0xFF;
    mh[2]=((dh.dsize & 0x0000FF00) >> 8) & 0xFF;
    mh[3]=dh.dsize & 0xFF;
    if(4!=fwrite(mh,1,4,fpW)) {
        fprintf(stderr,"ERROR: Could not write size.\n");
        goto ENDPROC;
    }

    /* PCM書き込み */
    if(dh.dsize!=fwrite(data,1,dh.dsize,fpW)) {
        fprintf(stderr,"ERROR: Could not write data.\n");
        goto ENDPROC;
    }

    rc=0;

    /* 終了処理 */
ENDPROC:
    if(data) {
        free(data);
    }
    if(fpR) {
        fclose(fpR);
    }
    if(fpW) {
        fclose(fpW);
    }
    return rc;
}
このコマンドを実行すれば、wav形式のファイルが、先頭32bit(4バイト)にアイキャッチ、次の32bit(4バイト)にサイズ、残りがPCMデータという非常にシンプルなデータ構造に変換されます。ちなみに、サイズ情報をビッグエンディアンに変換しているのはナンセンスです。私の自前のツールの仕様の関係で、そういう仕様にしています。(PCMデータそのものはリトルエンディアンです)

(4)効果音はストアすること
AndroidやiPhoneの標準のAPIでは、wavファイルをそのまま発音する機能があります。
しかし、効果音というのは何回も繰り返し再生されるものなので、再生の都度、ファイルを読み込むというのは、処理効率が悪すぎます。
VGSの場合、全リソースデータ(ROMファイル)の内容を、起動時に一括でメモリ領域に展開していて、以降はメモリ領域を参照することでデータアクセスを行っています。これにより、リアルタイム性の高い効果音の発音処理を実現しています。ゲームにとっての効果音は、BGM以上に重要な存在なので、効果音データは必ず事前にストア済みの情報を使わなければなりません。

0 件のコメント:

コメントを投稿