tsugaru’s blog

主に技術的なことをつぶやきます。プログラミング,ポケモンGO,音楽,アニメ等

WAVファイルを覗いてみた

音を分析するために、ファイル形式を知る必要があると思い、調べてみた。 音の非圧縮ファイルWAVは、RIFFを継承したファイル構造をとる。

ファイル形式の基本情報

IFF(Interchange File Format)

エレクトロニック・アーツ社がコモドールのAmigaというコンピュータゲーム向けに異なるアプリケーション間でのデータ転送を用意にするために開発された。詳しくはRIFFで。

RIFF(Resource Interchange File Format)

形式はIFFと全く同じであり、エンディアンが、IFFではAmigaMacintoshで使われるため(関連:AIFF)、ビッグエンディアンであるが、RIFFはIBMのPCのx86プロセッサに合わせて、多バイト整数をリトルエンディアン形式(後ろのバイトデータが前に記録)で格納することが異なる。 音以外にも使われる、フォーマットのフォーマットのようなもの。 chunk(チャンク)というデータ単位によって構成される。 定義はMultimedia Programming Interface and Data Specificationsに書いてある。

RIFFファイルの構成

以下のような情報単位、チャンクを持ち、チャンク内にサブチャンクを持つこともできる。

ID(識別子) 4byte チャンクを識別するためのASCIIコード4文字分データ fmt,dataなど FourCCと呼ばれる(MacではOSType)。
Size(サイズ) 4byte データサイズ(byte)を指定する。リトルエンディアン32bit整数(IDとSizeを省いた大きさ)
Date(データ) nbyte データ本体
(パディング) (1byte) (チャンク長が偶数バイトでない場合に1バイト追加される)

なお、ファイル全体は一つのRIFFチャンクで構成されるので、RIFF形式のファイルは、 元の4byteで「RIFF」、次の4byteでサイズの情報を必ず持つ。 また、チャンクごとに、データフィールドのはじめに追加フィールドが存在することがある。

RIFFチャンク

RIFFチャンクはDataの最初の4バイトにフォームタイプを追加フィールドとして持つ。フォームタイプは、ファイルに格納されているデータの形式を識別する4文字のコードで、たとえば、Microsoft波形オーディオファイルのフォームタイプは「WAVE」となる。 ほかに、PAL (RIFF Palette Format),RDIB( RIFF Device Independent Bitmap Format), RMID (RIFF MIDI Format),RMMP (RIFF Multimedia Movie File Format),RTF(Rich Text Format),BFF(Bundle File Format)などがある。

Rich Text Format

Microsoft Word Technical Reference: For Windows and OS/2で述べられている。書籍なので購入するしかない。

Waveform Audio File Format (WAVE)

https://sites.google.com/site/musicgapi/technical-documents/wav-file-format https://www.recordingblogs.com/wiki/wave-file-format

音に関する様々な情報を含むチャンク。特にfmtチャンク、dataチャンクは必須に近いほど重要。順番は どちらでも良いが、処理における慣習上、fmtチャンクをdataチャンクより先におくべき。逆にするとストリーミング再生が、最後まで読み取られてからでないとできなくなってしまう。

fmtチャンク

WAVEチャンクに含まれる。 マイクロソフトの_WAVEFORMATEX構造体に相当。 https://docs.microsoft.com/ja-jp/windows/win32/wmdm/-waveformatex

オフセット サイズ (Field) 説明
0x00 4 チャンクID(fmt) 「fmt」 | (0x666D7420)
0x04 4 チャンクデータサイズ(FormatTagが1の時、通常16) 16 +追加のフォーマットバイト
0x08 2 wFormatTag 圧縮コード 1〜65,535
0x0a 2 wChannels チャンネル数 1〜65,535
0x0c 4 dwSamplesPerSec サンプルレート 1-0xFFFFFFFF
0x10 4 dwAvgBytesPerSec 1秒あたりの平均バイト数 (ストリーミング再生でデータを読み込むべき速度) 1-0xFFFFFFFF
0x14 2 wBlockAlign ブロック整列 (1サンプルあたりのバイト数) 1〜65,535

ここで、AvgBytesPerSec=SamplesPerSec*BlockAlgnである。

wFormatTagについて

value(16進数) Format Category
WAVE_FORMAT_PCM (0x0001) Microsoft Pulse Code Modulation (PCM) format
IBM_FORMAT_MULAW (0x0101) IBM mu-law format
IBM_FORMAT_ALAW (0x0102) IBM a-law format
IBM_FORMAT_ADPCM (0x0103) IBM AVC Adaptive

他にも富士通NECYAMAHAらしきものもあったが、https://www.recordingblogs.com/wiki/format-chunk-of-a-wave-file を参照のこと。

特に圧縮コードが1(Line PCM)の時、以下の項目が上に追加される。

オフセット サイズ (Field) 説明
0x16 2 wBitsPerSample サンプルあたりの重要なビット 2-65,535

また、このとき、 BlockAlign = SignificantBitsPerSample / 8 * Channels

以下の項目が上に追加されることもある。追加されているかは、datasizeを確認すればわかるハズ。

オフセット サイズ (Field) 説明
0x18 2 cbSize 追加のフォーマットバイト 0〜65,535

wFormatTagの追加属性を格納できる。 wFormatTagが追加情報を必要としない場合、このメンバーは0に設定する必要がある。

dataチャンク

チャンクに新しいフィールドはない。複数のチャンネルを持つ場合、一つのサンプルごとにチャンネル別のデータを 持つような配列の配列をデータとしている。つまり「sample_1_ch_1,sample_1_ch_2,sample_2_ch_1,...」というかんじ。 量子化数によって、符号ありかどうか変わることに注意。8bitの場合は、符号なし整数である。

other

他にもWAVEformatのチャンクではfactチャンク、silentチャンク、instrumentチャンク、playlistチャンクなどを持つことがある。

LISTチャンク

LISTチャンクはDataの最初の4バイトにリストタイプを追加フィールドとして持つ。 こちらのチャンクは複数のチャンクをまとめた総称としてのタイプであり、例えばリストタイプが INFOのLISTチャンクは、著作権および作成日の情報を提供する「ICOP」および「ICRD」チャンクを含めることができる。

waveファイルをみてみる。

https://maoudamashii.jokersounds.com/list/se2.html よりワンポイント31のwave形式をダウンロードした。

バイナリコードを16進数煮直してみる。 Macの場合、ターミナルで簡単にバイナリを確認する 方法がある。 結局、はじめRIFFのヘッダに12byte,fmtチャンクに24byte、dataのヘッダに8byte使うので、データ本体は 45byte目からあることになる。

xxd -bits filename.wavの結果。左端はバイト数、右端はasciiコードへの変換を表ている。

00000000: 01010010 01001001 01000110 01000110 10101000 10100111  RIFF..
00000006: 00000101 00000000 01010111 01000001 01010110 01000101  ..WAVE
0000000c: 01100110 01101101 01110100 00100000 00010000 00000000  fmt ..
00000012: 00000000 00000000 00000001 00000000 00000010 00000000  ......
00000018: 01000100 10101100 00000000 00000000 00010000 10110001  D.....
0000001e: 00000010 00000000 00000100 00000000 00010000 00000000  ......
00000024: 01100100 01100001 01110100 01100001 11100000 10100110  data..
...

hexdump filename.wavの結果。

0000000 52 49 46 46 a8 a7 05 00 57 41 56 45 66 6d 74 20
0000010 10 00 00 00 01 00 02 00 44 ac 00 00 10 b1 02 00
0000020 04 00 10 00 64 61 74 61 e0 a6 05 00 11 00 0d 00
...

xxd filename.wavの結果

00000000: 5249 4646 a8a7 0500 5741 5645 666d 7420  RIFF....WAVEfmt 
00000010: 1000 0000 0100 0200 44ac 0000 10b1 0200  ........D.......
00000020: 0400 1000 6461 7461 e0a6 0500 1100 0d00  ....data........
...

以上から適切なデータ形式に変換した結果は以下のようになる。

項目 根拠
chunkID RIFF形式 (52(R)49(I)46(F)46(F)
サイズ 370600bytes (a8a7 0500 リトルエンディアンなので、1バイトごとの塊を反転させて0005a7a8)
format type WAVEformat (57415645)
chunkID fmt chunk(66(f)6D(m)74(t)20(space))
size 16Bytes (1000 0000 to 00000010)
圧縮コード LPCM (0100 to 0001)
ch 2ch (0200 to 0002)
サンプリング周波数 44100Hz (44ac 0000 to 0000ac44)
1秒あたりの平均バイト数 176400bytes (10b1 0200 to 0002b110)
1サンプルあたりのバイト数 4bytes (0400 to 0004)
サンプルあたりの重要なビット 16bits (1000 to 0010)
chunkID data 64617461
size 370400 e0a6 0500
data本体 1100...以下

なお、ここで、176400bytes =44100Hz 4bytes と、4bytes =16bits/82chが確かに成立していることがわかる。 また、Macにおいて、情報をみてみると、種類:WAVEオーディオ、サイズ:370,608 バイト(ヘッダのぶん多い)、オーディオチャンネル:ステレオ、 サンプルレート:44.1kHz,ビット/サンプル:16となっていて、正しいと言える。 そのほかの情報(作成日など)はファイルが保有しているのではなく、OSやファイルソフトウェアが保有している。 今の状態では、dataがどのように保存されているかわかりにくい。したがって、わかりやすいデータについて調べてみた。

以下のデータはおよそ440Hzの台形波がなるように人工的に作った音声ファイルである。(Excelで学ぶフーリエ変換の教材を流用) dataよりあとで、同じ並びが繰り返されていることがうかがえる。

00000000: 5249 4646 6aac 0000 5741 5645 666d 7420  RIFFj...WAVEfmt 
00000010: 1000 0000 0100 0100 2256 0000 44ac 0000  ........"V..D...
00000020: 0200 1000 6461 7461 46ac 0000 e0b1 f0d8  ....dataF.......
00000030: 0000 1027 204e 204e 204e 204e 204e 204e  ...' N N N N N N
00000040: 204e 204e 204e 204e 204e 204e 204e 204e   N N N N N N N N
00000050: 204e 204e 204e 204e 204e 204e 204e 1027   N N N N N N N.'
00000060: 0000 f0d8 e0b1 e0b1 e0b1 e0b1 e0b1 e0b1  ................
00000070: e0b1 e0b1 e0b1 e0b1 e0b1 e0b1 e0b1 e0b1  ................
00000080: e0b1 e0b1 e0b1 e0b1 e0b1 e0b1 e0b1 e0b1  ................
00000090: f0d8 0000 1027 204e 204e 204e 204e 204e  .....' N N N N N
000000a0: 204e 204e 204e 204e 204e 204e 204e 204e   N N N N N N N N
000000b0: 204e 204e 204e 204e 204e 204e 204e 204e   N N N N N N N N
000000c0: 204e 1027 0000 f0d8 e0b1 e0b1 e0b1 e0b1   N.'............
000000d0: e0b1 e0b1 e0b1 e0b1 e0b1 e0b1 e0b1 e0b1  ................
000000e0: e0b1 e0b1 e0b1 e0b1 e0b1 e0b1 e0b1 e0b1  ................
000000f0: e0b1 e0b1 f0d8 0000 1027 204e 204e 204e  .........' N N N
...

以上から適切なデータ形式に変換した結果は以下のようになる。

項目 根拠
chunkID RIFF形式 (52(R)49(I)46(F)46(F)
サイズ 44138bytes 6aac 0000
format type WAVEformat (57415645)
chunkID fmt chunk(66(f)6D(m)74(t)20(space))
size 16Bytes (1000 0000 to 00000010)
圧縮コード LPCM (0100 to 0001)
ch 1ch (0100 to 0001)
サンプリング周波数 22050Hz 2256 0000
1秒あたりの平均バイト数 44100bytes 44ac 0000
1サンプルあたりのバイト数 2bytes (0200 to 0002)
サンプルあたりの重要なビット 16bits (1000 to 0010)
chunkID data 64617461
size 44102 46ac 0000
data本体 e0b1 f0d8 以下

ここで、繰り返した部分をみてみると、

F0D80000 1027204E 204E204E 204E204E 204E204E 204E204E 204E204E 204E204E 204E204E 204E204E 204E204E 204E204E 204E1027 0000F0D8 E0B1E0B1 E0B1E0B1 E0B1E0B1 E0B1E0B1 E0B1E0B1 E0B1E0B1 E0B1E0B1 E0B1E0B1 E0B1E0B1 E0B1E0B1 E0B1E0B1

であり、チャンネルが1でサンプルあたりのビットが16bit(2byte)であることを考えると、一つのサンプルあたり16進数において、 4桁=4bit*4桁=16bitで一つ分となる。ここで、特に204Eは20000、E0B1は-20000であり、0000は0、1027は10000、F0D8は-10000、であることを 考えると、確かに台型波を表現していると言える。また、50サンプリングデータごとに繰り返しているので、サンプリング周波数が22050Hzであることを 考えると、出力される音程は22050/50=441Hzと言える。確かに狙い通りのデータを表現していると言える。

実際に音データを利用していく際は、ここから離散コサインフーリエ変換の技術と、人間の耳の特性を利用してさらに圧縮して mp3ファイルなどにして保存される。

参考文献