フェルマータ

個人用のメモ。ソフトウェアの導入とかが多くなる予定。

libsndfile + QIODevice で WAVE ファイルの読み書き

経緯

 趣味で音声合成っぽいソフトウェアを作っている。いい加減音声ファイルの読み書き部を作らねばならなくなったのだが、C++ WAVE 読み込み 無料Google 先生に聞いてもマッチョな結果しか返ってこない。これはこれで興味はあるが、音声合成ドメインに信号列の処理は含まれても信号の入出力自体は含まれないのでもっと手軽に読み書きしたい。

 なので OSS 、ただし GPL は除く、で良いライブラリがあるのが理想である。と言うかなんでみんなフォーマットとバイナリ読むことばっかに興味あるんだ。普通にいろいろ簡単に読み込めるライブラリをくれよ。

libsndfile インストール

 いくつか探したのだが libsndfile が良さそう。ライセンスは LGPL で配布はバイナリとソースコード。以下からダウンロードできる。

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から読み込むことができるのでそれなりに恩恵がありそう。あとは適当にインタフェースを整えてやればよい。僕の実装は以下にあるのでご参考までに。