]> git.tdb.fi Git - libs/al.git/commitdiff
Make sound format support optional master
authorMikko Rasa <tdb@tdb.fi>
Thu, 6 Jun 2019 11:07:44 +0000 (14:07 +0300)
committerMikko Rasa <tdb@tdb.fi>
Thu, 6 Jun 2019 11:19:03 +0000 (14:19 +0300)
Since there are now two different decoders, it makes sense to allow
selecting only one of them.

Build
source/mad/mp3decoder.cpp [new file with mode: 0644]
source/mad/mp3decoder.h [new file with mode: 0644]
source/mp3decoder.cpp [deleted file]
source/mp3decoder.h [deleted file]
source/oggdecoder.cpp [deleted file]
source/oggdecoder.h [deleted file]
source/sounddecoder.cpp
source/vorbis/oggdecoder.cpp [new file with mode: 0644]
source/vorbis/oggdecoder.h [new file with mode: 0644]

diff --git a/Build b/Build
index 02ebe328ea842528567fd0f4575f7e67f0637c99..0f79322ba9a843c6ff8ac5690362e5fb997ee32a 100644 (file)
--- a/Build
+++ b/Build
@@ -3,17 +3,40 @@ package "mspal"
        version "0.10";
        description "C++ wrapper for OpenAL";
 
        version "0.10";
        description "C++ wrapper for OpenAL";
 
-       // TODO make these features
-       require "vorbisfile";
-       require "mad";
        require "openal";
        require "sigc++-2.0";
        require "mspcore";
        require "mspdatafile";
 
        require "openal";
        require "sigc++-2.0";
        require "mspcore";
        require "mspdatafile";
 
+       feature "libvorbis" "Include libvorbis support for decoding OGG files"
+       {
+               default "yes";
+       };
+       if_feature "libvorbis"
+       {
+               require "vorbisfile";
+       };
+
+       feature "libmad" "Include libmad support for decoding MP3 files"
+       {
+               default "yes";
+       };
+       if_feature "libmad"
+       {
+               require "mad";
+       };
+
        library "mspal"
        {
                source "source";
        library "mspal"
        {
                source "source";
+               if_feature "libvorbis"
+               {
+                       source "source/vorbis";
+               };
+               if_feature "libmad"
+               {
+                       source "source/mad";
+               };
                install true;
                install_map
                {
                install true;
                install_map
                {
diff --git a/source/mad/mp3decoder.cpp b/source/mad/mp3decoder.cpp
new file mode 100644 (file)
index 0000000..cc32356
--- /dev/null
@@ -0,0 +1,191 @@
+#include <limits>
+#include <mad.h>
+#include <msp/strings/format.h>
+#include "mp3decoder.h"
+
+using namespace std;
+
+namespace {
+
+Msp::Int16 scale(mad_fixed_t sample)
+{
+       if(sample<=-MAD_F_ONE)
+               return numeric_limits<Msp::Int16>::min();
+       else if(sample>=MAD_F_ONE)
+               return numeric_limits<Msp::Int16>::max();
+       else
+               return (sample + (1<<(MAD_F_FRACBITS-16))) >> (MAD_F_FRACBITS-15);
+}
+
+} // namespace
+
+
+namespace Msp {
+namespace AL {
+
+mp3_error::mp3_error(const std::string &func, int err):
+       runtime_error(format("%s: %s", func, get_message(err)))
+{ }
+
+string mp3_error::get_message(int err)
+{
+       return format("%d", err);
+}
+
+
+struct Mp3Decoder::Private
+{
+       mad_stream stream;
+       mad_frame frame;
+       mad_synth synth;
+};
+
+
+Mp3Decoder::Mp3Decoder(IO::Seekable &io):
+       priv(new Private),
+       in(io),
+       in_buf_size(16384),
+       in_buffer(new char[in_buf_size]),
+       read_pos(0)
+{
+       mad_stream_init(&priv->stream);
+       mad_frame_init(&priv->frame);
+       mad_synth_init(&priv->synth);
+
+       try
+       {
+               if(!fill_input() || !decode(false))
+                       throw runtime_error("no mp3 data");
+
+               format = create_format(2, MAD_NCHANNELS(&priv->frame.header));
+               freq = priv->frame.header.samplerate;
+       }
+       catch(...)
+       {
+               delete[] in_buffer;
+               throw;
+       }
+}
+
+Mp3Decoder::~Mp3Decoder()
+{
+       delete[] in_buffer;
+       mad_synth_finish(&priv->synth);
+       mad_frame_finish(&priv->frame);
+       mad_stream_finish(&priv->stream);
+       delete priv;
+}
+
+bool Mp3Decoder::detect(const string &sig)
+{
+       if(sig.size()>=2 && sig[0]=='\xFF' && (sig[1]&0xE0)==0xE0)
+               return true;
+
+       static const char id3_sig[] = { 'I', 'D', '3' };
+       if(sig.size()<sizeof(id3_sig))
+               return false;
+       return !sig.compare(0, sizeof(id3_sig), id3_sig, sizeof(id3_sig));
+}
+
+void Mp3Decoder::seek(unsigned pos)
+{
+       unsigned unit = get_unit_size(format);
+       pos /= unit;
+
+       in.seek(0, IO::S_BEG);
+       mad_stream_buffer(&priv->stream, 0, 0);
+       fill_input();
+       if(!pos)
+       {
+               priv->synth.pcm.length = 0;
+               return;
+       }
+
+       while(decode(false))
+       {
+               unsigned frame_len = 32*MAD_NSBSAMPLES(&priv->frame.header);
+               if(frame_len>pos)
+               {
+                       decode(true);
+                       read_pos = pos;
+                       break;
+               }
+
+               pos -= frame_len;
+       }
+}
+
+unsigned Mp3Decoder::read(char *buf, unsigned len)
+{
+       unsigned nchan = get_n_channels(format);
+       mad_pcm &pcm = priv->synth.pcm;
+
+       unsigned pos = 0;
+       while(pos+2*nchan<=len)
+       {
+               if(read_pos>=pcm.length && !decode(true))
+                       break;
+
+               for(unsigned i=0; i<nchan; ++i)
+               {
+                       Int16 sample = scale(pcm.samples[i][read_pos]);
+                       buf[pos++] = sample;
+                       buf[pos++] = sample>>8;
+               }
+
+               ++read_pos;
+       }
+
+       return pos;
+}
+
+bool Mp3Decoder::fill_input()
+{
+       unsigned in_pos = 0;
+       if(priv->stream.next_frame && priv->stream.next_frame<priv->stream.bufend)
+       {
+               copy(priv->stream.next_frame, priv->stream.bufend, in_buffer);
+               in_pos = priv->stream.bufend-priv->stream.next_frame;
+       }
+
+       unsigned len = in.read(in_buffer+in_pos, in_buf_size-in_pos);
+       in_pos += len;
+
+       mad_stream_buffer(&priv->stream, reinterpret_cast<unsigned char *>(in_buffer), in_pos);
+       priv->stream.error = MAD_ERROR_NONE;
+
+       return len;
+}
+
+bool Mp3Decoder::try_decode(bool full)
+{
+       if(full)
+               return !mad_frame_decode(&priv->frame, &priv->stream);
+       else
+               return !mad_header_decode(&priv->frame.header, &priv->stream);
+}
+
+bool Mp3Decoder::decode(bool full)
+{
+       while(!try_decode(full))
+       {
+               if(priv->stream.error==MAD_ERROR_BUFLEN)
+               {
+                       if(!fill_input())
+                               return false;
+               }
+               else if(!MAD_RECOVERABLE(priv->stream.error))
+                       throw mp3_error("mad_frame_decode", priv->stream.error);
+       }
+
+       if(full)
+       {
+               read_pos = 0;
+               mad_synth_frame(&priv->synth, &priv->frame);
+       }
+
+       return true;
+}
+
+} // namespace AL
+} // namespace Msp
diff --git a/source/mad/mp3decoder.h b/source/mad/mp3decoder.h
new file mode 100644 (file)
index 0000000..ce0c468
--- /dev/null
@@ -0,0 +1,54 @@
+#ifndef MSP_AL_MP3DECODER_H_
+#define MSP_AL_MP3DECODER_H_
+
+#include <stdexcept>
+#include <msp/al/sounddecoder.h>
+
+namespace Msp {
+namespace AL {
+
+class mp3_error: public std::runtime_error
+{
+public:
+       mp3_error(const std::string &, int);
+       virtual ~mp3_error() throw() { }
+
+private:
+       static std::string get_message(int);
+};
+
+
+/**
+Decoder for MPEG-2 audio files, a.k.a. MP2 and MP3 files.  Normally you should
+use one of the SoundDecoder::open_* functions to create a decoder.
+*/
+class Mp3Decoder: public SoundDecoder
+{
+private:
+       struct Private;
+
+       Private *priv;
+       IO::Seekable &in;
+       unsigned in_buf_size;
+       char *in_buffer;
+       unsigned read_pos;
+
+public:
+       Mp3Decoder(IO::Seekable &);
+       virtual ~Mp3Decoder();
+
+       static bool detect(const std::string &);
+
+       virtual void seek(unsigned);
+       virtual unsigned read(char *, unsigned);
+
+private:
+       bool fill_input();
+       bool try_decode(bool);
+       bool decode(bool);
+};
+
+} // namespace AL
+} // namespace Msp
+
+#endif
diff --git a/source/mp3decoder.cpp b/source/mp3decoder.cpp
deleted file mode 100644 (file)
index cc32356..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-#include <limits>
-#include <mad.h>
-#include <msp/strings/format.h>
-#include "mp3decoder.h"
-
-using namespace std;
-
-namespace {
-
-Msp::Int16 scale(mad_fixed_t sample)
-{
-       if(sample<=-MAD_F_ONE)
-               return numeric_limits<Msp::Int16>::min();
-       else if(sample>=MAD_F_ONE)
-               return numeric_limits<Msp::Int16>::max();
-       else
-               return (sample + (1<<(MAD_F_FRACBITS-16))) >> (MAD_F_FRACBITS-15);
-}
-
-} // namespace
-
-
-namespace Msp {
-namespace AL {
-
-mp3_error::mp3_error(const std::string &func, int err):
-       runtime_error(format("%s: %s", func, get_message(err)))
-{ }
-
-string mp3_error::get_message(int err)
-{
-       return format("%d", err);
-}
-
-
-struct Mp3Decoder::Private
-{
-       mad_stream stream;
-       mad_frame frame;
-       mad_synth synth;
-};
-
-
-Mp3Decoder::Mp3Decoder(IO::Seekable &io):
-       priv(new Private),
-       in(io),
-       in_buf_size(16384),
-       in_buffer(new char[in_buf_size]),
-       read_pos(0)
-{
-       mad_stream_init(&priv->stream);
-       mad_frame_init(&priv->frame);
-       mad_synth_init(&priv->synth);
-
-       try
-       {
-               if(!fill_input() || !decode(false))
-                       throw runtime_error("no mp3 data");
-
-               format = create_format(2, MAD_NCHANNELS(&priv->frame.header));
-               freq = priv->frame.header.samplerate;
-       }
-       catch(...)
-       {
-               delete[] in_buffer;
-               throw;
-       }
-}
-
-Mp3Decoder::~Mp3Decoder()
-{
-       delete[] in_buffer;
-       mad_synth_finish(&priv->synth);
-       mad_frame_finish(&priv->frame);
-       mad_stream_finish(&priv->stream);
-       delete priv;
-}
-
-bool Mp3Decoder::detect(const string &sig)
-{
-       if(sig.size()>=2 && sig[0]=='\xFF' && (sig[1]&0xE0)==0xE0)
-               return true;
-
-       static const char id3_sig[] = { 'I', 'D', '3' };
-       if(sig.size()<sizeof(id3_sig))
-               return false;
-       return !sig.compare(0, sizeof(id3_sig), id3_sig, sizeof(id3_sig));
-}
-
-void Mp3Decoder::seek(unsigned pos)
-{
-       unsigned unit = get_unit_size(format);
-       pos /= unit;
-
-       in.seek(0, IO::S_BEG);
-       mad_stream_buffer(&priv->stream, 0, 0);
-       fill_input();
-       if(!pos)
-       {
-               priv->synth.pcm.length = 0;
-               return;
-       }
-
-       while(decode(false))
-       {
-               unsigned frame_len = 32*MAD_NSBSAMPLES(&priv->frame.header);
-               if(frame_len>pos)
-               {
-                       decode(true);
-                       read_pos = pos;
-                       break;
-               }
-
-               pos -= frame_len;
-       }
-}
-
-unsigned Mp3Decoder::read(char *buf, unsigned len)
-{
-       unsigned nchan = get_n_channels(format);
-       mad_pcm &pcm = priv->synth.pcm;
-
-       unsigned pos = 0;
-       while(pos+2*nchan<=len)
-       {
-               if(read_pos>=pcm.length && !decode(true))
-                       break;
-
-               for(unsigned i=0; i<nchan; ++i)
-               {
-                       Int16 sample = scale(pcm.samples[i][read_pos]);
-                       buf[pos++] = sample;
-                       buf[pos++] = sample>>8;
-               }
-
-               ++read_pos;
-       }
-
-       return pos;
-}
-
-bool Mp3Decoder::fill_input()
-{
-       unsigned in_pos = 0;
-       if(priv->stream.next_frame && priv->stream.next_frame<priv->stream.bufend)
-       {
-               copy(priv->stream.next_frame, priv->stream.bufend, in_buffer);
-               in_pos = priv->stream.bufend-priv->stream.next_frame;
-       }
-
-       unsigned len = in.read(in_buffer+in_pos, in_buf_size-in_pos);
-       in_pos += len;
-
-       mad_stream_buffer(&priv->stream, reinterpret_cast<unsigned char *>(in_buffer), in_pos);
-       priv->stream.error = MAD_ERROR_NONE;
-
-       return len;
-}
-
-bool Mp3Decoder::try_decode(bool full)
-{
-       if(full)
-               return !mad_frame_decode(&priv->frame, &priv->stream);
-       else
-               return !mad_header_decode(&priv->frame.header, &priv->stream);
-}
-
-bool Mp3Decoder::decode(bool full)
-{
-       while(!try_decode(full))
-       {
-               if(priv->stream.error==MAD_ERROR_BUFLEN)
-               {
-                       if(!fill_input())
-                               return false;
-               }
-               else if(!MAD_RECOVERABLE(priv->stream.error))
-                       throw mp3_error("mad_frame_decode", priv->stream.error);
-       }
-
-       if(full)
-       {
-               read_pos = 0;
-               mad_synth_frame(&priv->synth, &priv->frame);
-       }
-
-       return true;
-}
-
-} // namespace AL
-} // namespace Msp
diff --git a/source/mp3decoder.h b/source/mp3decoder.h
deleted file mode 100644 (file)
index 01fba63..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-#ifndef MSP_AL_MP3DECODER_H_
-#define MSP_AL_MP3DECODER_H_
-
-#include <stdexcept>
-#include "sounddecoder.h"
-
-namespace Msp {
-namespace AL {
-
-class mp3_error: public std::runtime_error
-{
-public:
-       mp3_error(const std::string &, int);
-       virtual ~mp3_error() throw() { }
-
-private:
-       static std::string get_message(int);
-};
-
-
-/**
-Decoder for MPEG-2 audio files, a.k.a. MP2 and MP3 files.  Normally you should
-use one of the SoundDecoder::open_* functions to create a decoder.
-*/
-class Mp3Decoder: public SoundDecoder
-{
-private:
-       struct Private;
-
-       Private *priv;
-       IO::Seekable &in;
-       unsigned in_buf_size;
-       char *in_buffer;
-       unsigned read_pos;
-
-public:
-       Mp3Decoder(IO::Seekable &);
-       virtual ~Mp3Decoder();
-
-       static bool detect(const std::string &);
-
-       virtual void seek(unsigned);
-       virtual unsigned read(char *, unsigned);
-
-private:
-       bool fill_input();
-       bool try_decode(bool);
-       bool decode(bool);
-};
-
-} // namespace AL
-} // namespace Msp
-
-#endif
diff --git a/source/oggdecoder.cpp b/source/oggdecoder.cpp
deleted file mode 100644 (file)
index 374cf0b..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-#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, &section);
-       if(res<0)
-               throw ogg_error("ov_read", res);
-       else if(res==0)
-               eof_flag = true;
-       return res;
-}
-
-} // namespace AL
-} // namespace Msp
diff --git a/source/oggdecoder.h b/source/oggdecoder.h
deleted file mode 100644 (file)
index e194736..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#ifndef MSP_AL_OGGDECODER_H_
-#define MSP_AL_OGGDECODER_H_
-
-#include <stdexcept>
-#include "sounddecoder.h"
-
-namespace Msp {
-namespace AL {
-
-class ogg_error: public std::runtime_error
-{
-public:
-       ogg_error(const std::string &, int);
-       virtual ~ogg_error() throw() { }
-
-private:
-       static std::string get_message(int);
-};
-
-
-/**
-Decoder for Ogg Vorbis files.  Normally you should use one of the
-SoundDecoder::open_* functions to create a decoder.
-*/
-class OggDecoder: public SoundDecoder
-{
-private:
-       struct Private;
-
-       Private *priv;
-
-public:
-       OggDecoder(IO::Seekable &);
-       ~OggDecoder();
-
-       static bool detect(const std::string &);
-
-       virtual void seek(unsigned);
-       virtual unsigned read(char *, unsigned);
-};
-
-} // namespace AL
-} // namespace Msp
-
-#endif
index e0a56d3a4fc1513d21e6b99eada7f26c262903db..ed9de800ffef84345faf133260080a9aa9eb6b96 100644 (file)
@@ -1,8 +1,12 @@
 #include <msp/core/refptr.h>
 #include <msp/io/file.h>
 #include <msp/strings/format.h>
 #include <msp/core/refptr.h>
 #include <msp/io/file.h>
 #include <msp/strings/format.h>
-#include "mp3decoder.h"
-#include "oggdecoder.h"
+#ifdef WITH_LIBMAD
+#include "mad/mp3decoder.h"
+#endif
+#ifdef WITH_LIBVORBIS
+#include "vorbis/oggdecoder.h"
+#endif
 #include "sounddecoder.h"
 
 using namespace std;
 #include "sounddecoder.h"
 
 using namespace std;
@@ -42,21 +46,25 @@ SoundDecoder *SoundDecoder::open_io(IO::Seekable &io)
        io.read(sig_buf, sizeof(sig_buf));
        io.seek(0, IO::S_BEG);
        string signature(sig_buf, sizeof(sig_buf));
        io.read(sig_buf, sizeof(sig_buf));
        io.seek(0, IO::S_BEG);
        string signature(sig_buf, sizeof(sig_buf));
+
+#ifdef WITH_LIBVORBIS
        if(OggDecoder::detect(signature))
                return new OggDecoder(io);
        if(OggDecoder::detect(signature))
                return new OggDecoder(io);
-       else if(Mp3Decoder::detect(signature))
+#endif
+
+#ifdef WITH_LIBMAD
+       if(Mp3Decoder::detect(signature))
                return new Mp3Decoder(io);
                return new Mp3Decoder(io);
-       else
+#endif
+
+       string sig_hex;
+       for(unsigned i=0; i<sizeof(sig_buf); ++i)
        {
        {
-               string sig_hex;
-               for(unsigned i=0; i<sizeof(sig_buf); ++i)
-               {
-                       if(i)
-                               sig_hex += ' ';
-                       sig_hex += Msp::format("%02X", static_cast<unsigned char>(sig_buf[i]));
-               }
-               throw unsupported_sound(sig_hex);
+               if(i)
+                       sig_hex += ' ';
+               sig_hex += Msp::format("%02X", static_cast<unsigned char>(sig_buf[i]));
        }
        }
+       throw unsupported_sound(sig_hex);
 }
 
 } // namespace AL
 }
 
 } // namespace AL
diff --git a/source/vorbis/oggdecoder.cpp b/source/vorbis/oggdecoder.cpp
new file mode 100644 (file)
index 0000000..374cf0b
--- /dev/null
@@ -0,0 +1,130 @@
+#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, &section);
+       if(res<0)
+               throw ogg_error("ov_read", res);
+       else if(res==0)
+               eof_flag = true;
+       return res;
+}
+
+} // namespace AL
+} // namespace Msp
diff --git a/source/vorbis/oggdecoder.h b/source/vorbis/oggdecoder.h
new file mode 100644 (file)
index 0000000..0d19d45
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef MSP_AL_OGGDECODER_H_
+#define MSP_AL_OGGDECODER_H_
+
+#include <stdexcept>
+#include <msp/al/sounddecoder.h>
+
+namespace Msp {
+namespace AL {
+
+class ogg_error: public std::runtime_error
+{
+public:
+       ogg_error(const std::string &, int);
+       virtual ~ogg_error() throw() { }
+
+private:
+       static std::string get_message(int);
+};
+
+
+/**
+Decoder for Ogg Vorbis files.  Normally you should use one of the
+SoundDecoder::open_* functions to create a decoder.
+*/
+class OggDecoder: public SoundDecoder
+{
+private:
+       struct Private;
+
+       Private *priv;
+
+public:
+       OggDecoder(IO::Seekable &);
+       ~OggDecoder();
+
+       static bool detect(const std::string &);
+
+       virtual void seek(unsigned);
+       virtual unsigned read(char *, unsigned);
+};
+
+} // namespace AL
+} // namespace Msp
+
+#endif