読者です 読者をやめる 読者になる 読者になる

ゆとりーなの日記

日記的な事を書いて行くと思はれる

OggVorbisデータをC++汎用ストリームでデコードするのは意󠄁外と厄介

OggVorbisのライブラリ(libogg, libvorbis, libvorbisfile)にはov_open_callbacksと云ふ函數があつて、これを使󠄁へばfopen以外でもoggファイルを讀む事が出來る。具󠄁體的にはfread, fseek, fclose, ftell相當の函數を自分󠄁で用意󠄁してov_callbacksに入れる。メモリに對して動作する此等の函數を用意󠄁すればファイルからでなはなくメモリから讀む事が可能になる。
C++にはbasic_istreamと云ふものがあり、これにはほぼC互換のread、seek、tellgがあるのでこれを使󠄁へばov_callbacksに渡す函數を樂に作れさうな氣がした。しかもboostにはbasic_ivectorstreamと云ふものがあるので連󠄀續領域メモリに對するstreamが簡單に用意󠄁出來ると云ふ筈であつた。

template <typename Stream>
size_t ReadStream(void * const ptr, const size_t size, const size_t nemmb, void * const datasource) {
  return static_cast<Stream *>(datasource)->read(static_cast<char *>(ptr), size * nemmb).gcount();
}

template <typename Stream>
int SeekStream(void * const datasource, const ogg_int64_t offset, const int whence) {
  switch (whence) {
    case SEEK_CUR:
      return static_cast<Stream *>(datasource)->seekg(offset, std::ios::cur) ? 0 : -1;
    case SEEK_END:
      return static_cast<Stream *>(datasource)->seekg(offset, std::ios::end) ? 0 : -1;
    case SEEK_SET:
      return static_cast<Stream *>(datasource)->seekg(offset, std::ios::beg) ? 0 : -1;
    default:
      return -1;
  }
}

template <typename Stream>
long TellStream(void * const datasource) {
  return static_cast<long>(static_cast<Stream *>(datasource)->tellg());
}

C++汎用ストリームを使󠄁つたread、seek、tellの實裝はこんな風に簡單に書ける。SEEK_CURとstd::ios::cur等の數値が同じかどうかは分󠄁からないのでswitchで分ける。closeはストリームが自動的にやるので不要󠄁である。

using IStream = boost::interprocess::basic_vectorstream<std::vector<char>>;
boost::interprocess::basic_vectorstream<std::vector<char>> data; // ファイルの中身を入れとく
OggVorbis_File ovf;
ov_open_callbacks(&data, &ovf, nullptr, 0, {&ReadStream<IStream>, &SeekStream<IStream>, nullptr, &TellStream<IStream>});

コールバック指定でOggVorbis_Fileを初期化󠄁して準備は完了。これを實際に使󠄁つてみる。

const auto info = ov_info(&ovf, -1);
const auto decodeSize = static_cast<DWORD>(ov_pcm_total(&ovf, -1)) * info->channels * 2;
std::vector<char> buf(decodeSize);
int bs;
DWORD totalReadSize = 0;
while (totalReadSize < decodeSize) {
  const auto readSize = ov_read(&ovf, buf.data() + totalReadSize, decodeSize - totalReadSize, 0, 2, 1, &bs);
  if (!readSize) {
    break;
  }
  totalReadSize += readSize;
}
std::cout << ov_pcm_seek(&ovf, 0) << std::endl;

デコードサイズを調󠄁べて全󠄁部バッファに讀込󠄁む。ループしたいとすると先頭にシークしたくなるのでシークする。するとここのシークでOV_EREAD(讀めなかつたエラー)が返󠄁つてくるのである。實際ブレイク仕掛けて追󠄁つてみると、ストリーム內部でeofbitとfailbitが立つてゐた。ストリームのreadは要󠄁求されたサイズ分󠄁讀めないとfailbitも立てると云ふ仕樣があるが、OggVorbisライブラリではファイルサイズを飛び越えて讀んで、實際讀込󠄁めたサイズを使󠄁ふと云ふ處理が含まれてゐると云ふ事なのだらうか(內部を追󠄁ひかけた訣ではないのでよく分󠄁らないが)。
因みにここで試しにSeekStreamで-1を返󠄁すのをやめるて全󠄁部0を返󠄁すやうにすると今度はOV_EOFが返󠄁つてくる。どうもeofbitが立つてゐてもシークは出來ないらしい。なのでどうしてもやるならeofとfailが立つてゐたらclearすると云ふ處理を入れれば一應動くやうにはなる。

template <typename Stream>
int SeekStream(void * const datasource, const ogg_int64_t offset, const int whence) {
  if (static_cast<Stream *>(datasource)->eof() && static_cast<Stream *>(datasource)->fail()) {
    static_cast<Stream *>(datasource)->clear();
  }
  switch (whence) {
    case SEEK_CUR:
      return static_cast<Stream *>(datasource)->seekg(offset, std::ios::cur) ? 0 : -1;
    case SEEK_END:
      return static_cast<Stream *>(datasource)->seekg(offset, std::ios::end) ? 0 : -1;
    case SEEK_SET:
      return static_cast<Stream *>(datasource)->seekg(offset, std::ios::beg) ? 0 : -1;
    default:
      return -1;
  }
}

しかしどうもコードがゴチャゴチャしてくるのが氣掛かりである。因みにこれで動くやうになるのはstd::ifstreamなどの場合だけで、boost::basic_ivectorstreamの場合は駄目である。boost::basic_ivectorstreamのclearはどうも內部コンテナのクリアとかをするメンバ函數なので、std::basic_iosのclearとは別物である。なんともやゝこしい。