AdventCalendar2020 Rust programming
Rustでwavを解析する その1 wavそれはというわけで, wavの解析をしていきたいと思います.
wavはオーディオデータを記述, 格納するためにMicrosoftとIBMによって作られたRIFFというフォーマットの一種です. wav以外にも, avi等にも利用されているこのRIFF, 中々厄介なフォーマットで, RIFFの仕様含め, 技術文献がMicrosoftのサイト内でも体系的なものをみつける事が困難ですが, 一定見付けられる範囲で参考文献を以下に上げておきます.
- RIFF
- Resource Interchange File Format Services
- WAVEFORMAT
- WAVEFORMAT structure (mmreg.h)
- WAVEFORMATEX
- WAVEFORMATEX structure
- Windows Multimedia
- Windows Multimedia
- AVI
- AVI RIFF File Reference
また, wav形式の拡張であるBWF形式のRIFFの資料も参考になります.
さて, RIFFはチャンクという構造を並べたフォーマットとなっており, FourCCと呼ばれるチャンクのマジックナンバー4バイトとチャンクサイズを示す4バイトから構成されるヘッダを必ず持っており, チャンクサイズの次のバイト列からRIFFのデータがサイズ分収まっています. 例外はRIFFチャンクとLISTチャンクで, この2つのチャンクはその中に1以上のチャンク(サブチャンク)を内包する事ができるチャンクとなっており そのサブチャンクの構成がどういった物であるかを示す識別子が, チャンクサイズを示すバイト列の次の4バイトのバイト列となっており, その次のバイト列からサブチャンクが並びます. なお, このチャンクサイズはFourCCとチャンクサイズ自身のサイズ(つまり合計8バイト)を含みません.
RIFFのデータを読み解く処理としては大枠以下のような流れになります.
- 先頭4バイトを取得し, RIFFのFourCCの有無を確認
- 次の4バイトを取得し, データサイズを確認
- 次の4バイトを取得し, RIFFのデータ形式が何であるかを確認
- 次の4バイトを取得し, サブチャンクが何であるかを確認
- 次の4バイトを取得し, データサイズを確認
- サブチャンクがLISTならば, 4に戻る
- 6. でないならば, 5のデータサイズまでバイト列をそのサブチャンクの種別毎に解析
- EOFまで4を繰り返す
wavの場合, RIFFデータの先頭から数えて9〜12バイト目(上記処理でいうと3)のバイトの並びが 'W''A''V''E'となっているか否かで判定されます. ここが'A''V''I'' 'であればそれはaviファイルなので今回の対象外となります.
wavのサブチャンクで必須なものは以下の識別子を持つものです.
- 'f''m''t'' '
- 'd''a''t''a'
dataチャンクは波形データそのものであり, fmtチャンクはdataチャンクのデータの形式がどういったものであるかを定義しています. fmtチャンクはdataチャンクよりも前にくる必要があります. そうしないとシーケンシャルにデータを読んだ時にこのdataがどういう形式で格納されているのかわからなくなるからです.
このfmtチャンクはdataチャンクがどういったものであれ, 共通のフォーマットとして定義できるものと, 個々の形式によって定義が異なるものがあり, この内共通で定義できるものが, 先に参考資料として上げた"WAVEFORMAT"です.
WAVEFORMATは以下のような構造になっています.
typedef struct waveformat_tag { WORD wFormatTag; WORD nChannels; DWORD nSamplesPerSec; DWORD nAvgBytesPerSec; WORD nBlockAlign; } WAVEFORMAT;
これは先の参考資料に記載あるCの構造体です. Visual Studioに同梱されるmmreg.hにも同様の記載があります.
各要素は概ね以下の通りです.
- wFormatTag
- フォーマットタグ. dataチャンクに含まれるデータのフォーマットタイプを示します.
- nChannels
- チャンネル数. モノラルは1, ステレオは2
- nSamplesPerSec
- サンプリングレート
- nAvgBytesPerSec
- 1秒あたりに要するバイト数. 16bit 48kHz ステレオの場合は48000*2*2=192kByte/sec.
- nBlockAlign
- ブロックサイズ. 1サンプルあたりの量子化ビット数のバイト表現 * チャンネル数. 16bit ステレオの場合は 2*2=4 Byte/sample.
基本的にはフォーマットタグでwavのフォーマットを判定し, そのフォーマットとチャンクサイズによってこの後取得するバイト列のサイズとデータ表現を判別します.
一般的な無圧縮PCMの場合, フォーマットタグは0x0001が設定される事になっています. これはmmreg.hに以下のように定義されています.
#define WAVE_FORMAT_PCM 1
他にもフォーマットタグとしては沢山の定義があり, mmreg.hには以下のような記載があります(これは定義の最初の部分のみです)
#define WAVE_FORMAT_UNKNOWN 0x0000 /* Microsoft Corporation */ #define WAVE_FORMAT_ADPCM 0x0002 /* Microsoft Corporation */ #define WAVE_FORMAT_IEEE_FLOAT 0x0003 /* Microsoft Corporation */ #define WAVE_FORMAT_VSELP 0x0004 /* Compaq Computer Corp. */ #define WAVE_FORMAT_IBM_CVSD 0x0005 /* IBM Corporation */ #define WAVE_FORMAT_ALAW 0x0006 /* Microsoft Corporation */ #define WAVE_FORMAT_MULAW 0x0007 /* Microsoft Corporation */ #define WAVE_FORMAT_DTS 0x0008 /* Microsoft Corporation */ #define WAVE_FORMAT_DRM 0x0009 /* Microsoft Corporation */ #define WAVE_FORMAT_WMAVOICE9 0x000A /* Microsoft Corporation */ #define WAVE_FORMAT_WMAVOICE10 0x000B /* Microsoft Corporation */
フォーマットタグがWAVE_FORMAT_PCMの場合, WAVEFORMATのデータ構造の次の2バイトは1サンプルあたりの量子化ビット数が入ります. Microsoftではこのデータまでを含めて以下のような構造体も定義しています.
typedef struct pcmwaveformat_tag { WAVEFORMAT wf; WORD wBitsPerSample; } PCMWAVEFORMAT;
フォーマットタグ次第では, この先のデータに追加情報などが入っている場合があります. それらについてはそのフォーマットタグ毎に構造体や処理をわける必要があります. が, 今回はひとまず無圧縮PCMについてのみ考えていこうと思います.
とはいっても, 無圧縮PCMの場合, 後はこれまでと同様にFourCCとチャンクサイズを判定してdataチャンクを読み取り, fmtチャンクの情報を元に波形データを解釈すれば良いです.
という処で, 次回から実際にwavのデータを読み取る物を構築していこうと思います.