libsndfile + QIODevice で WAVE ファイルの読み書き
経緯
趣味で音声合成っぽいソフトウェアを作っている。いい加減音声ファイルの読み書き部を作らねばならなくなったのだが、C++ WAVE 読み込み 無料
と Google 先生に聞いてもマッチョな結果しか返ってこない。これはこれで興味はあるが、音声合成のドメインに信号列の処理は含まれても信号の入出力自体は含まれないのでもっと手軽に読み書きしたい。
なので OSS 、ただし GPL は除く、で良いライブラリがあるのが理想である。と言うかなんでみんなフォーマットとバイナリ読むことばっかに興味あるんだ。普通にいろいろ簡単に読み込めるライブラリをくれよ。
libsndfile インストール
いくつか探したのだが libsndfile が良さそう。ライセンスは LGPL で配布はバイナリとソースコード。以下からダウンロードできる。
- mega-nerd - libsndfile http://www.mega-nerd.com/libsndfile/
Windows のバイナリは VC++ 用なので MINGW 使いはソースコードのコンパイルをしないといけない。
MINGW + Windows 7 の組み合わせでは MSYS からインストールすれば良い。
適当に MSYS のコンソールから伝統的な./configure && make && make install
でコンパイルしてインストールできるので、 Windows でも簡単に使える。
libsndfile の使い方
普通に使う分には以下のブログ記事が役に立つ。
読んでいただければ分かるが以下のように読み込みを行う。
SndfileHandle sndfile("sample.wav", SFM_READ); sndfile.samplerate(); // サンプリング周波数 sndfile.channels(); // チャンネル数 short int* input = new short int [sndfile.frames()]; sndfile.readf(input, sndfile.frames()); // 16bit 符号付き整数の信号
ファイルから読み込むのであればこれだけで多くのフォーマットに対応できる。ライセンスも良い感じなのだが、しかしながら基本的なインタフェースはファイルしか無い。別にそれほど不自由はないが、できればQIODevice
のようなインタフェースから読める方が拡張性が高いし安心できる。
QIODevice からファイルを読み込む。
libsndfile 的にはQIODevice
からの読み込みは仮想ファイルからの読み込みという扱いになる。この機能は残念ながら C++ 版の libsndfile にはなく、 C 版のインタフェースを使うしか無い。
SF_INFO info{0, 0, 0, 0, 0, 0}; // ファイルの情報が入る構造体 SF_VIRTUAL_IO io = // 仮想 IO のコールバック関数の設定 { callbackLength, callbackSeek, callbackRead, callbackWrite, callbackTell }; QIODevice device; device.open(QIODevice::ReadOnly); // somehow open the device! SNDFILE *file = sf_open_virtual(&io, SFM_READ, &info, &data);
読み込む部分はこれだけで良い。ただしsf_open_virtual
を使うためにはSF_VIRTUAL_IO
に記述されたコールバック関数を実装せねばならない。すなわち以下の部分になる。
sf_count_t callbackLength(void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->bytesAvailable(); } sf_count_t callbackSeek(sf_count_t offset, int whence, void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; switch(whence) { case SEEK_CUR: device->seek(device->pos() + offset); break; case SEEK_SET: device->seek(offset); break; case SEEK_END: device->seek(offset + callbackLength(voidDevice)); break; } return device->pos(); } sf_count_t callbackRead(void *ptr, sf_count_t count, void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->read((char *)ptr, count); } sf_count_t callbackWrite(const void *ptr, sf_count_t count, void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->write((const char *)ptr, count); } sf_count_t callbackTell(void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->pos(); }
ただしこのやり方はストリーミングは一切考慮していない。とはいえQIODevice
から読み込むことができるのでそれなりに恩恵がありそう。あとは適当にインタフェースを整えてやればよい。僕の実装は以下にあるのでご参考までに。