WAVファイルを手軽にきちんと扱いたい!
WAVファイルを手軽にきちんと扱いたくなったので仕様や巷の実装を調べていました。
WAVファイルはチャンク構造になっていて、内部データ表現を自由に選ぶ事ができます。
自由に選ぶ事が出来ると言う事は、それなりにきちんと設計実装したプログラムでなければ正しく扱えない事を意味します。
手元にあった書籍(名前は出しませんが)や巷の実装を見ると、随分と厳しい暗黙の前提条件が用いられていて、色々なWAVファイルをあまり正しく読める事を期待できないなぁという感じでした。
また、データ保持の方式が「スタックにずんっと置く」ような実装だったり、「ファイルサイズに応じて超巨大なメモリをヒープから取る」ような実装だったりと、いくら巨大なメモリがPCに搭載されている昨今とは言え、何だかなぁ~という感じです。
そこで、巷の実装例を少し紹介した後で、設計実装したWAVファイルライブラリを御紹介します。
巷の実装を見てみる
今回設計したライブラリの紹介の前に、巷の実装を見てみましょう。
実装をそのまま掲載する事はできないので、ちょっと修正してあります。
巷の実装例(とある書籍のサンプルコード)
この実装は、出現するチャンク構造の順序に暗黙の前提条件が使用されている例です。
fp = fopen(file_name, "rb");
fread(riff_chunk_ID, 1, 4, fp);
fread(&riff_chunk_size, 4, 1, fp);
fread(riff_form_type, 1, 4, fp);
fread(fmt_chunk_ID, 1, 4, fp);
fread(&fmt_chunk_size, 4, 1, fp);
fread(&fmt_wave_format_type, 2, 1, fp);
fread(&fmt_channel, 2, 1, fp);
fread(&fmt_samples_per_sec, 4, 1, fp);
fread(&fmt_bytes_per_sec, 4, 1, fp);
fread(&fmt_block_size, 2, 1, fp);
fread(&fmt_bits_per_sample, 2, 1, fp);
fread(data_chunk_ID, 1, 4, fp);
fread(&data_chunk_size, 4, 1, fp);
この実装の場合、出現するチャンク構造の順序が異なるだけで全く正しい処理ができません。
そして、この後の処理が以下のようになっています。
pcm->fs = fmt_samples_per_sec;
pcm->bits = fmt_bits_per_sample;
pcm->length = data_chunk_size / 2;
pcm->s = calloc(pcm->length, sizeof(double));
data_chunk_sizeに期待するチャンク構造のチャンク・データ・サイズが格納されているとは限りません。そしてその値をそのまま使ってcallocしているので危ないです。
このコードは落第点です。
でも音を扱うという書籍のサンプル実装です。
この実装の危ない点は、間違っていても処理がどんどん進んでしまう事です。
そして、間違っている場合、何がどう間違っているのか検知していないので、APIの外から見た場合にどうしようもありません。
巷の適当に実装されたコードは意外にこういうのばかりであまり再利用には向いていません。
仮に入門者がこのコードを見て育った時に、「いつこの設計実装レベルから抜け出せるのだろう?」と考えると少し心配になってしまいます。
Tiny WAV I/O Moduleプロジェクト
Tiny WAV I/O Moudleプロジェクトとは、小規模組み込みシステムで使用可能なWAVファイルライブラリを作ろうという目的で作られたプロジェクトです。
今回実装したライブラリは、その設計の前段にあたるもので、「どういったインターフェースなら使いやすいかなぁ」というのを検討する為のもの。libcが使用可能な環境でのみ使用可能です。成果物はTiny WAV I/O Moduleプロジェクトのlibc-basedに追加しました。
まぁ、ざっくり言うとlibc-basedは「パソコンで使えるお手軽WAVライブラリ」ですね。
wavfileモジュールの特徴
以下に今回設計実装したお手軽WAVライブラリ、wavfileモジュールの特徴を示します。
- 省メモリ設計。(ライブラリ側では巨大なメモリを要求しない。)
- ファイル形式に依らず0.0から1.0で正規化されたデータ入出力インターフェースを採用。
- 複数チャネルデータに対応。
- ヘッダの実装詳細を把握しなくても使える。
データをどこに配置するのかについてはアプリケーション層が決めたい事の一つです。
巨大なメモリを勝手にアロケーションするようなライブラリは使いづらくて仕方ありません。
よってwavfileモジュールでは、これらに関知しないようにインターフェースを設計しました。
また、WAVファイルの処理を実装する時に意外に面倒なのが、データ形式の違いです。
今回のwavfileモジュールでは、ファイルのデータ形式によらず0.0から1.0で正規化されたデータ入出力インターフェースを採用しました。これによって、「8ビット形式のファイルは0から255で1バイトだよね。」とか「16ビット形式のファイルは-32768から32767で2バイトだよね。」とか考えなくて済みます。とにかくサンプルを1つ得るインターフェースを呼ぶだけで良いのです。
typedef struct {
uint16_t num_channels;
double channel_data[WAVFILE_MAXIMUM_CHANNELS];
} wavfile_data_t;
wavfile_read_dataを呼ぶと上記の構造体にサンプルデータが格納されて返ってきます。
channel_dataの中身は先の規格化された値が入っている事になります。
wavfileモジュールのインターフェース
wavfileモジュールを使うために必要なインターフェースは、リード用、ライト用と合わせてもたったの6つです。
WAVFILE *wavfile_open(const char *filename, WavFileMode mode, WavFileResult *result);
WavFileResult wavfile_read_info(WAVFILE *p, wavfile_info_t *info);
WavFileResult wavfile_read_data(WAVFILE *p, wavfile_data_t *data);
WavFileResult wavfile_write_info(WAVFILE *p, const wavfile_info_t *info);
WavFileResult wavfile_write_data(WAVFILE *p, const wavfile_data_t *data);
WavFileResult wavfile_close(WAVFILE *p);
基本思想
infoとdataの2段階で簡単に実現しちゃうよ!というのが本ライブラリの基本思想です。
読み込み
- wavfile_openにファイル名とWavFileModeReadを与えてオープン。
- wavfile_read_infoでヘッダ情報を読み込む。
- wavfile_read_dataでデータを読み込む。
(必要に応じて繰り返す)
- wavfile_closeでクローズ。
書き込み
- wavfile_openにファイル名とWavFileModeWriteを与えてオープン。
- ヘッダ情報を設定する。
- wavfile_write_infoでヘッダ情報を書き込む。
- データを設定する。
- wavfile_write_dataでデータを書き込む。(必要に応じて繰り返す)
- wavfile_closeでクローズ。
どんな風に使えるの?
単にデータを読みたい場合
よくあるやりたい仕事の1つが、とにかくデータを読んでみたい!というものです。
Tiny WAV I/O Moduleのlibc-based実装を使えば簡単に実現できてしまいます。
WavFileResult result;
wavfile_info_t info;
wavfile_data_t data;
WAVFILE *wf = wavfile_open("YourWavFileName.WAV", WavFileModeRead, &result);
if (wf != NULL) {
wavfile_read_info(wf, &info);
while (1) {
wavfile_read_data(wf, &data);
if (data.num_channels == 0) {
// 読むべきデータが無くなったらチャネル数に0を返してくる。
break;
}
// ここでデータを確認すれば良い。
}
wavfile_close(wf);
}
ソースコード
ソースコードは、以下からダウンロード可能です。
http://pt.sourceforge.jp/projects/tinywavio/
ライセンスはMITです。
今後の計画
最近は色々と忙しくて基板設計まで手が回らないので、今年は中途半端に拘るのを諦め、外販されているモジュールを活用してシステム物をやりたいなぁと計画しています。
実は、金子システム株式会社さんから
UMB-SSM2603なるものが販売されています。
そうなのです。
このモジュールは
ACB-BF592と組み合わせてもミニサイズなのです。
動作も高速なので、実は今回のlibc-basedな実装もそのまま動かせたりして?!なんて甘い考えを持っていたりします。ファイルシステム周辺は何らかの抽象化層に乗せようかなぁ。
とにかく組み込み装置としてエレガントにWAVを扱えるようしていこうと考えています。