]> git.tdb.fi Git - libs/al.git/commitdiff
Add support for MP3 files using libmad
authorMikko Rasa <tdb@tdb.fi>
Wed, 20 Jun 2018 10:33:20 +0000 (13:33 +0300)
committerMikko Rasa <tdb@tdb.fi>
Wed, 20 Jun 2018 10:33:20 +0000 (13:33 +0300)
Build
source/mp3decoder.cpp [new file with mode: 0644]
source/mp3decoder.h [new file with mode: 0644]
source/sounddecoder.cpp

diff --git a/Build b/Build
index 4517ad5151587beebba25104e46a921a9851e20e..02ebe328ea842528567fd0f4575f7e67f0637c99 100644 (file)
--- a/Build
+++ b/Build
@@ -3,7 +3,9 @@ package "mspal"
        version "0.10";
        description "C++ wrapper for OpenAL";
 
+       // TODO make these features
        require "vorbisfile";
+       require "mad";
        require "openal";
        require "sigc++-2.0";
        require "mspcore";
diff --git a/source/mp3decoder.cpp b/source/mp3decoder.cpp
new file mode 100644 (file)
index 0000000..a717cf4
--- /dev/null
@@ -0,0 +1,160 @@
+#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
+       {
+               decode_frame();
+               format = (priv->frame.header.mode==MAD_MODE_SINGLE_CHANNEL ? MONO16 : STEREO16);
+               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);
+}
+
+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);
+}
+
+void Mp3Decoder::rewind()
+{
+       in.seek(0, IO::S_BEG);
+}
+
+unsigned Mp3Decoder::read(char *buf, unsigned len)
+{
+       unsigned nchan = (format==STEREO16 ? 2 : 1);
+       mad_pcm &pcm = priv->synth.pcm;
+
+       unsigned pos = 0;
+       while(pos+2*nchan<=len)
+       {
+               if(read_pos>=pcm.length)
+               {
+                       if(!decode_frame())
+                               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::decode_frame()
+{
+       while(1)
+       {
+               if(!priv->stream.buffer || priv->stream.error==MAD_ERROR_BUFLEN)
+                       if(!fill_input())
+                               return false;
+
+               if(!mad_frame_decode(&priv->frame, &priv->stream))
+               {
+                       mad_synth_frame(&priv->synth, &priv->frame);
+                       read_pos = 0;
+                       return true;
+               }
+
+               if(priv->stream.error==MAD_ERROR_BUFLEN)
+                       continue;
+               else if(!MAD_RECOVERABLE(priv->stream.error))
+                       throw mp3_error("mad_frame_decode", priv->stream.error);
+       }
+}
+
+} // namespace AL
+} // namespace Msp
diff --git a/source/mp3decoder.h b/source/mp3decoder.h
new file mode 100644 (file)
index 0000000..06060de
--- /dev/null
@@ -0,0 +1,49 @@
+#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);
+};
+
+
+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 rewind();
+       virtual unsigned read(char *, unsigned);
+
+private:
+       bool fill_input();
+       bool decode_frame();
+};
+
+} // namespace AL
+} // namespace Msp
+
+#endif
index a2cd51af4624154cca52312157cfb18f31a9b21e..38ae8fa15b77635e360ee50d313345eee6edb37b 100644 (file)
@@ -1,6 +1,7 @@
 #include <msp/core/refptr.h>
 #include <msp/io/file.h>
 #include <msp/strings/format.h>
+#include "mp3decoder.h"
 #include "oggdecoder.h"
 #include "sounddecoder.h"
 
@@ -42,6 +43,8 @@ SoundDecoder *SoundDecoder::open_io(IO::Seekable &io)
        string signature(sig_buf, sizeof(sig_buf));
        if(OggDecoder::detect(signature))
                return new OggDecoder(io);
+       else if(Mp3Decoder::detect(signature))
+               return new Mp3Decoder(io);
        else
        {
                string sig_hex;