--- /dev/null
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
+#include <vorbis/vorbisfile.h>
+#pragma GCC diagnostic pop
+#include <msp/strings/format.h>
+#include "oggdecoder.h"
+
+using namespace std;
+
+namespace {
+
+size_t read(void *ptr, size_t size, size_t nmemb, void *src)
+{
+ Msp::IO::Base *in = reinterpret_cast<Msp::IO::Base *>(src);
+ unsigned len = in->read(reinterpret_cast<char *>(ptr), size*nmemb);
+ return len/size;
+}
+
+int seek(void *src, ogg_int64_t offset, int whence)
+{
+ Msp::IO::SeekType type;
+ if(whence==SEEK_SET)
+ type = Msp::IO::S_BEG;
+ else if(whence==SEEK_CUR)
+ type = Msp::IO::S_CUR;
+ else if(whence==SEEK_END)
+ type = Msp::IO::S_END;
+ else
+ return -1;
+
+ Msp::IO::Seekable *in = reinterpret_cast<Msp::IO::Seekable *>(src);
+ return in->seek(offset, type);
+}
+
+long tell(void *src)
+{
+ return reinterpret_cast<Msp::IO::Seekable *>(src)->tell();
+}
+
+ov_callbacks io_callbacks =
+{
+ &read,
+ &seek,
+ 0,
+ &tell
+};
+
+} // namespace
+
+
+namespace Msp {
+namespace AL {
+
+ogg_error::ogg_error(const std::string &func, int code):
+ runtime_error(format("%s: %s", func, get_message(code)))
+{ }
+
+string ogg_error::get_message(int code)
+{
+ switch(code)
+ {
+ case OV_FALSE: return "No data available";
+ case OV_HOLE: return "Missing or corrupt data";
+ case OV_EREAD: return "Read error";
+ case OV_EFAULT: return "Internal inconsistency";
+ case OV_EIMPL: return "Not implemented";
+ case OV_EINVAL: return "Invalid argument";
+ case OV_ENOTVORBIS: return "Not Vorbis data";
+ case OV_EBADHEADER: return "Corrupt Vorbis header";
+ case OV_EVERSION: return "Unsupported version";
+ case OV_EBADLINK: return "Bad link";
+ case OV_ENOSEEK: return "Stream is not seekable";
+ default: return format("Unknown error (%d)", code);
+ }
+}
+
+
+struct OggDecoder::Private
+{
+ OggVorbis_File ovfile;
+};
+
+OggDecoder::OggDecoder(IO::Seekable &io):
+ priv(new Private)
+{
+ int ret = ov_open_callbacks(&io, &priv->ovfile, 0, 0, io_callbacks);
+ if(ret<0)
+ throw ogg_error("ov_open_callbacks", ret);
+
+ vorbis_info *info = ov_info(&priv->ovfile, -1);
+ freq = info->rate;
+ format = create_format(2, info->channels);
+
+ size = ov_pcm_total(&priv->ovfile, 0)*get_unit_size(format);
+}
+
+OggDecoder::~OggDecoder()
+{
+ if(priv->ovfile.datasource)
+ ov_clear(&priv->ovfile);
+ delete priv;
+}
+
+bool OggDecoder::detect(const std::string &sig)
+{
+ static const char ogg_sig[] = { 'O', 'g', 'g', 'S' };
+ if(sig.size()<sizeof(ogg_sig))
+ return false;
+ return !sig.compare(0, sizeof(ogg_sig), ogg_sig, sizeof(ogg_sig));
+}
+
+void OggDecoder::seek(unsigned pos)
+{
+ pos /= get_unit_size(format);
+ ov_pcm_seek(&priv->ovfile, pos);
+}
+
+unsigned OggDecoder::read(char *buf, unsigned len)
+{
+ int section = 0;
+ int res = ov_read(&priv->ovfile, buf, len, 0, 2, 1, §ion);
+ if(res<0)
+ throw ogg_error("ov_read", res);
+ else if(res==0)
+ eof_flag = true;
+ return res;
+}
+
+} // namespace AL
+} // namespace Msp