2012/06/10

C言語 wavファイルのヘッダを読む

オーディオフォーマットであるwavのヘッダーを読むプログラムを作ってみる。Windowsなら普通はAPIを使って、こんな部分は自分で作らないものだけど、wavの勉強と、プラットフォーム非依存という意味もあって作ってみた。でも、きっちり作るのは面倒なので、自分が使っているwavの範囲で動けばよい。読み込みたいデータはfmtだけ。fmt(オーディオファオーマット)には基本的な、チャンネル数、ステレオ/モノラル、サンプリング周波数、ビット数などが書かれている。他にもいろいろな情報が書けるようだが、LISTぐらいしか見たことがないので、LISTが出てきたら、その部分をスキップして、データの部分に飛ばすというプログラム。実際に使う場合は、ヘッダにある基本情報を取得することと、データの開始位置を把握するのが目的。その予備実験。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* format chunk 構造体 */
struct FormatChunk {
  char id[5];
  unsigned int size;
  unsigned short format;
  unsigned short channels;
  unsigned int  samplerate;
  unsigned int  bytepersec;
  unsigned short blockalign;
  unsigned short bit;
}fmtchunk;

/* バッファー 構造体 */
struct Buff {
  FILE *fpr;
  unsigned char bufchar;
  unsigned char buf[5];
  unsigned int bufint;
  unsigned short bufshort;
} buff;

/* 文字列 4byte ビッグエンディアン*/
void readchar(){
  int i,size;
  size = fread(buff.buf, sizeof(unsigned char), 4, buff.fpr);
}

/* 数値 4byte リトルエンディアン */
int readint(){
  fread(&buff.bufint, sizeof(unsigned int), 1, buff.fpr);
  return buff.bufint;
}

/* 数値 2byte リトルエンディアン */
int readshort(){
  fread(&buff.bufshort, sizeof(unsigned short), 1, buff.fpr);
  return buff.bufshort;
}
/* data */
void data(){
   int ret;
   for(;;){
      fread(&buff.bufchar, sizeof(unsigned char), 1, buff.fpr);
      if(buff.bufchar == 'd'){
        fseek(buff.fpr, -1, SEEK_CUR);
        readchar();
        ret = strcmp(buff.buf,"data");
        if(ret == 0){
             printf("[%s]\n",buff.buf);
             readint();
             printf("size: %d byte\n",buff.bufint);
             break;
        }
      }
   }
}

/* LIST */
void list(){
  int skip;
   readchar();
   int ret;
   ret = strcmp(buff.buf,"LIST");
   if(ret == 0){
      printf("[%s]\n",buff.buf);
      skip = readint();
      printf("size: %d byte\n",skip);
      /* skip処理 */
      fseek(buff.fpr, skip, SEEK_CUR);
   }else{
     fseek(buff.fpr, -4, SEEK_CUR);
   }
}
/* fmt view */
void fmtview(){
  printf("[%s]\n"
         "Size:%d byte\n"
         "AudioFormat: %d\n"
         "Channels: %d\n"
         "Samling Rate: %d Hz\n"
         "ByteRate: %d\n"
         "BlockAlign: %d\n"
         "BitsPerSample: %d bit\n",
          fmtchunk.id,
          fmtchunk.size,
          fmtchunk.format,
          fmtchunk.channels,
          fmtchunk.samplerate,
          fmtchunk.bytepersec,
          fmtchunk.blockalign,
          fmtchunk.bit);
}
/* fmt 読込み */
void fmt(){
  /* id fmt */
  readchar();
  memcpy(fmtchunk.id,buff.buf,sizeof(buff.buf));
  /* size */
  fmtchunk.size = readint();
  /* PCM 2byte */
  fmtchunk.format = readshort();
  /* channels 2byte */
  fmtchunk.channels = readshort();
  /* samples/sec 4byte 44100Hz */
  fmtchunk.samplerate = readint();
  /* svg.bytes/sec 4byte */
  fmtchunk.bytepersec = readint();
  /* bloack align 2byte */
  fmtchunk.blockalign = readshort();
  /* bits/sample 2byte 32bit */
  fmtchunk.bit = readshort();
}

/* ヘッド読込 */
int headread(void){
  int ret;
  /* rb バイナリとして開く */
  char *str = "sample.wav";
  buff.fpr = fopen(str, "rb"); 
  printf("%s\n",str);
  /* ファイルがない場合のエラー処理 */
  if (buff.fpr == NULL) { 
      printf("error");
      exit(EXIT_FAILURE);
  }else{
    /* RIFF size */
    readchar();
    ret = strcmp(buff.buf,"RIFF");
    if(ret != 0){
       printf("not RIFF");
       exit(EXIT_FAILURE);
    }
    printf("[%s]\n",buff.buf);
    readint();
    printf("size = %d byte\n",buff.bufint);
    /* WAVEファイルかどうかの確認 */
    readchar();
    ret = strcmp(buff.buf,"WAVE");
    if(ret != 0){
        printf("not WAVE");
        exit(EXIT_FAILURE);
    }
    printf("[%s]\n",buff.buf);
    /* fmt オーディオフォーマット */
    fmt();
    fmtview();
    /* LIST スキップ処理 */
    list();
    /* data size 音声ファイルスタート地点 */
    data();
    printf("data start point: %d byte\n",ftell(buff.fpr));
    /* ファイルを閉じる */
    fclose(buff.fpr);
  }
}

/* メイン関数 */
int main(void){
  headread();
  return 0;
}

上のソースではexeと同じ階層にsample.wavというオーディオファイルがあることを前提としている。その上で実行すると、sample.wavのヘッダーに書かれている内容が表示される。表示と一部のデータ取得が目的なので、書込みなど、使い回しが効かない。プログラムはダラダラと無駄に長いソースになってしまった。エラーチェックとかちゃんとやっていないので、あまりよろしくない状態。実際に使う場合は、いろいろ手を加えることにする。fmtのデータは構造体に保存するようにした。それ以外は内容を表示するだけ。LIST項目はタグ情報などが書き込まれるのだが、加工には必要ないので、スキップして、データ開始位置まで飛ばしている。データ開始位置の特定は文字 'd' をバイト単位で探して、発見したら "data" かどうか文字列のチェックをしている。dataであれば、サイズをチェックして、そこをスタート地点とし、終了する。
sample.wav
[RIFF]
size: 1222802 byte
[WAVE]
[fmt ]
Size: 16 byte
AudioFormat: 1
Channels: 1
Samling Rate: 44100 Hz
ByteRate: 176400
BlockAlign: 4
BitsPerSample: 32 bit
[LIST]
size: 74 byte
[data]
size: 1222684 byte
data start point: 126 byte
wavフォーマット参考資料
http://en.wikipedia.org/wiki/WAV
Audio File Format Specifications

C言語 ANSI C89 Meadow & MinGW GCC 目次はこちら