Skip to main content

WAVファイルフォーマットの読み込み

ファイルフォーマットの構造

WAVファイル全体はたくさんのチャンク(Chunk)で構成されています。

wave_chunk

構造体の項目はC言語のデータ型です。

  • char: 1バイト
  • short/ushort: 2バイト
  • int/uint: 4バイト

Chunkの役割と中身は自由に定義できますが、先頭のいくつかのChunkの意味は、WAVファイル自体の識別用のため固定されています。他のChunkはアプリケーションのため自由に拡張できます。WindowsとMac OSもそれぞれ微妙に違うChunkを持っていますが、基本の部分の定義が統一されていますので互換性はあります。

Waveフォーマットの基本Chunk構成は以下の通りです。

wave_chunk_2

どんなカスタマイズされたWaveファイルでも、図の中の太字の部分であるRIFF Chunk、Wave Format ChunkとWave Data Chunkが必ず存在するので、この三つのChunkを読込するとWaveファイルのデータを解析できます。

Chunk定義

Chunkの先頭に二つの要素、tagとsizeがあります。この二つの要素はどのChunkでも必ず先頭にあります。それぞれでChunkの意味と長さを示します。tagはcharの配列で構成され、長さは4です。全てのChunkは4個のアルファベットで区別するということです。このChunkヘッダーは、プログラムでは以下の通りに定義できます。

typedef struct _chunk
{
  char id[4];
  uint size;
} ChunkHead;

Riff Chunk

Wavファイルでは先頭のChunk 1は必ずRiffというChunkです。このChunkの定義は以下の通りです。

riff_chunk

Chunkヘッダーの部分以外、bodyはformatという一つの要素しかありません。そしてこのformatという要素も、4個のアルファベットで構成されます。プログラムの定義は以下の通りです。

typedef struct _riffChunk
{
  ChunkHead head;
  char format[4];
} RiffChunk;

Riff Chunkの内容は固定されています。それぞれ必ず「RIFF」、「4」、「WAVE」です。この内容があっていない場合、不正なWAVファイルフォーマットとして判断してもよいです。

Wave Format Chunk

2番目以降、Waveフォーマットを記載するためのChunkがあります。但しこのChunkは必ず2番目とは限りません。アプリケーションによって違う順番でChunkを保存することが可能ですので、プログラムでWaveファイルを読み込みする際に、順番に柔軟に対応できるループで読み込んだほうが良いでしょう。

wav_fmt_chunk

Wave Format Chrunkのtagは「fmt 」に固定されます。最後の一つのアルファベットはスペースであり、全体も4個で構成されます。

Riff Chunk以外、他のChunkの長さは固定ではないので、読込の際に先にChunkの長さを読み込んで、そしてその長さの通りにデータ量を読み込めます。このWave Format Chunkの長さもアプリケーション毎に指定できますが、先頭の六つの要素(青い部分)は固定されていますので、この部分のみを読み込めば、Wave内容の解析などの普通の対応には十分です。

Waveフォーマットを定義する部分(青い部分)は他の処理にいろいろ使えるので取り出して定義するほうが汎用性がよいです。

typedef struct _wavFmt
{
  ushort audioFormat;
  ushort channels;
  uint samplePerSecond;
  uint bytesPerSecond;
  ushort blockAlign;
  ushort bitsPerSample;
} WaveFileFormat;

このChunkの定義は以下の通りです。

typedef struct _wavFmtChunk
{
  ChunkHead chunk;
  WaveFileFormat format;
} WaveFormatChunk;

Wave Data Chunk

次はWaveのデータを読み込みます。Wave Data Chunkのtagは「data」となり、Chunk全体の定義は以下の通りです。

wav_data_chunk

かなり単純な構造体で、Chunkヘッダーとデータの内容だけで構成されています。データの内容を普通のメモリ領域を確保して保存すれば大丈夫です。

読込処理

フローチャートは以下の通りです。

read_wave_flow

黄色部分は実際の三つのRIFF Chunk、Wave Format ChunkとWave Data Chunkの読込処理です。この三つのChunkを全て正しく読み込んだ場合、Waveファイルを正しく認識したと判断します。プログラムをご参照ください。

// ファイルを開ける
FILE* file = NULL;
_tfopen_s(&file, filepath, _T("rb"));

// RIFF Chunkを読み込む
RiffChunk riff;
fread(&riff, sizeof(RiffChunk), 1, file);

// RIFF Chunkのtagが「RIFF」以外の場合、フォーマットエラーとみなす
if (strncmp(riff.head.id, "RIFF", 4) != 0)
{
  fclose(file);
  return NULL;
}

// RIFF Chunkのformatが「WAVE」以外の場合、フォーマットエラーとする
if (strncmp(riff.format, "WAVE", 4) != 0)
{
  fclose(file);
  return NULL;
}

ChunkHead chunk;

while (!feof(file))
{
  // 一つのChunkHeadを読み込む
  ZeroMemory(&chunk, sizeof(ChunkHead));
  fread(&chunk, sizeof(ChunkHead), 1, file);

  // Chunkのサイズが0より小さい場合エラーとする
  // (ファイルフォーマットが正しくない場合)
  if (chunk.size < 0) break;

  if (strncmp(chunk.id, "fmt ", 4) == 0)
  {
    // Wave Format Chunkを読み込み
    WaveFileFormat format;
    fread(&format, min(chunk.size, sizeof(WaveFileFormat)) , 1, file);

    // 先頭6つの要素以外の要素があれば、そのデータを無視してカーソルを移動させる
    fseek(file, chunk.size - sizeof(WaveFileFormat), SEEK_CUR);
  }
  else if (strncmp(chunk.id, "data", 4) == 0)
  {
    // Waveのデータを読み込む
    char* buffer = new char[chunk.size];
    fread(buffer, chunk.size, 1, file);
  }
  else
  {
    // 認識できないChunkをSkipしてカーソルを移動させる
    fseek(file, chunk.size, SEEK_CUR);
  }
}

// ファイルを閉じる
fclose(file);

Wave音声の秒数を求める

Waveのフォーマット(Wave Format Chunk)を正しく読み込みましたら、次の式で音声データの秒数を求めます。

double secondLength = waveDataChunk.size / waveFormatChunk.bytesPerSecond / waveFormatChunk.channels;

アプリ

このコードを利用して作成したWAVファイルの編集・再生ソフトがあります。簡易FFT解析も搭載しています。GitHubからソースコードを取得できます。

git clone https://github.com/jingwood/waveform-box.git

最後更新:2018年11月27日

Jingwood

北海道の田舎で暮らしているプログラマーです。最近山登りにハマりました。

Leave a Reply

Your email address will not be published. Required fields are marked *