From: Mikko Rasa Date: Thu, 6 Jun 2019 11:07:44 +0000 (+0300) Subject: Make sound format support optional X-Git-Url: http://git.tdb.fi/?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;p=libs%2Fal.git Make sound format support optional Since there are now two different decoders, it makes sense to allow selecting only one of them. --- diff --git a/Build b/Build index 02ebe32..0f79322 100644 --- a/Build +++ b/Build @@ -3,17 +3,40 @@ 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"; 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"; + if_feature "libvorbis" + { + source "source/vorbis"; + }; + if_feature "libmad" + { + source "source/mad"; + }; install true; install_map { diff --git a/source/mad/mp3decoder.cpp b/source/mad/mp3decoder.cpp new file mode 100644 index 0000000..cc32356 --- /dev/null +++ b/source/mad/mp3decoder.cpp @@ -0,0 +1,191 @@ +#include +#include +#include +#include "mp3decoder.h" + +using namespace std; + +namespace { + +Msp::Int16 scale(mad_fixed_t sample) +{ + if(sample<=-MAD_F_ONE) + return numeric_limits::min(); + else if(sample>=MAD_F_ONE) + return numeric_limits::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()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>8; + } + + ++read_pos; + } + + return pos; +} + +bool Mp3Decoder::fill_input() +{ + unsigned in_pos = 0; + if(priv->stream.next_frame && priv->stream.next_framestream.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(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 index 0000000..ce0c468 --- /dev/null +++ b/source/mad/mp3decoder.h @@ -0,0 +1,54 @@ +#ifndef MSP_AL_MP3DECODER_H_ +#define MSP_AL_MP3DECODER_H_ + +#include +#include + +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 ∈ + 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 index cc32356..0000000 --- a/source/mp3decoder.cpp +++ /dev/null @@ -1,191 +0,0 @@ -#include -#include -#include -#include "mp3decoder.h" - -using namespace std; - -namespace { - -Msp::Int16 scale(mad_fixed_t sample) -{ - if(sample<=-MAD_F_ONE) - return numeric_limits::min(); - else if(sample>=MAD_F_ONE) - return numeric_limits::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()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>8; - } - - ++read_pos; - } - - return pos; -} - -bool Mp3Decoder::fill_input() -{ - unsigned in_pos = 0; - if(priv->stream.next_frame && priv->stream.next_framestream.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(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 index 01fba63..0000000 --- a/source/mp3decoder.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef MSP_AL_MP3DECODER_H_ -#define MSP_AL_MP3DECODER_H_ - -#include -#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 ∈ - 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 index 374cf0b..0000000 --- a/source/oggdecoder.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-variable" -#include -#pragma GCC diagnostic pop -#include -#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(src); - unsigned len = in->read(reinterpret_cast(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(src); - return in->seek(offset, type); -} - -long tell(void *src) -{ - return reinterpret_cast(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()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 diff --git a/source/oggdecoder.h b/source/oggdecoder.h deleted file mode 100644 index e194736..0000000 --- a/source/oggdecoder.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef MSP_AL_OGGDECODER_H_ -#define MSP_AL_OGGDECODER_H_ - -#include -#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 diff --git a/source/sounddecoder.cpp b/source/sounddecoder.cpp index e0a56d3..ed9de80 100644 --- a/source/sounddecoder.cpp +++ b/source/sounddecoder.cpp @@ -1,8 +1,12 @@ #include #include #include -#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; @@ -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)); + +#ifdef WITH_LIBVORBIS 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); - else +#endif + + string sig_hex; + for(unsigned i=0; i(sig_buf[i])); - } - throw unsupported_sound(sig_hex); + if(i) + sig_hex += ' '; + sig_hex += Msp::format("%02X", static_cast(sig_buf[i])); } + throw unsupported_sound(sig_hex); } } // namespace AL diff --git a/source/vorbis/oggdecoder.cpp b/source/vorbis/oggdecoder.cpp new file mode 100644 index 0000000..374cf0b --- /dev/null +++ b/source/vorbis/oggdecoder.cpp @@ -0,0 +1,130 @@ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#include +#pragma GCC diagnostic pop +#include +#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(src); + unsigned len = in->read(reinterpret_cast(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(src); + return in->seek(offset, type); +} + +long tell(void *src) +{ + return reinterpret_cast(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()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 diff --git a/source/vorbis/oggdecoder.h b/source/vorbis/oggdecoder.h new file mode 100644 index 0000000..0d19d45 --- /dev/null +++ b/source/vorbis/oggdecoder.h @@ -0,0 +1,45 @@ +#ifndef MSP_AL_OGGDECODER_H_ +#define MSP_AL_OGGDECODER_H_ + +#include +#include + +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