]> Dogcows Code - chaz/yoink/blobdiff - src/moof/sound.cc
resource loading bugs and new sound class
[chaz/yoink] / src / moof / sound.cc
index a1da62b8be10c9d1c2567f2931e90a1eb9841071..853c21a3d806f92bbb48fb91cb188b6d70e5c678 100644 (file)
 #include <string>
 
 #include <boost/algorithm/string.hpp>
+#include <boost/cstdint.hpp>
 #include <AL/al.h>
 #include <AL/alc.h>
 #include <vorbis/codec.h>
 #include <vorbis/vorbisfile.h>
 
+#include "hash.hh"
 #include "log.hh"
 #include "manager.hh"
 #include "sound.hh"
+#include "resource.hh"
 #include "timer.hh"
 
-#define BUF_SIZE (64 * 1024)
-//#define BUF_SIZE (5*2048)
+
+#ifndef BUF_SIZE
+#define BUF_SIZE       (4096)
+#endif
+
+#define NUM_BUFFERS (16)
+
 
 namespace moof {
 
 
-class impl
+class sound_backend
+{
+public:
+
+       sound_backend()
+       {
+               if (retain_count++ == 0)
+               {
+                       al_device  = alcOpenDevice(0);
+                       al_context = alcCreateContext(al_device, 0);
+                       if (!al_device || !al_context)
+                       {
+                               const char* error = alcGetString(al_device,
+                                                                                                alcGetError(al_device));
+                               log_error("audio subsystem initialization failure", error);
+                       }
+                       else
+                       {
+                               alcMakeContextCurrent(al_context);
+                               log_info << "opened sound device `"
+                                                << alcGetString(al_device,
+                                                                                ALC_DEFAULT_DEVICE_SPECIFIER)
+                                                << "'" << std::endl;
+                       }
+               }
+       }
+
+
+       sound_backend(const sound_backend& backend)
+       {
+               ++retain_count;
+       }
+
+       sound_backend& operator=(const sound_backend& backend)
+       {
+               ++retain_count;
+               return *this;
+       }
+
+       ~sound_backend()
+       {
+               if (--retain_count == 0)
+               {
+                       alcMakeContextCurrent(0);
+                       alcDestroyContext(al_context);
+                       alcCloseDevice(al_device);
+                       log_info("unloaded sound device ALSA");
+               }
+       }
+
+
+       static int                      retain_count;
+       static ALCdevice*       al_device;
+       static ALCcontext*      al_context;
+};
+
+int                    sound_backend::retain_count = 0;
+ALCdevice*     sound_backend::al_device;
+ALCcontext*    sound_backend::al_context;
+
+
+class sound_resource;
+typedef resource_handle<sound_resource> sound_handle;
+
+
+class sound_resource_loader
 {
 public:
        
-       impl()
+       sound_resource_loader()
        {
-               resource::register_type<sound_stream>("ogg");
+               resource::register_type<sound_resource>("ogg");
        }
 
-       ~impl()
+       ~sound_resource_loader()
        {
                resource::unregister_type("ogg");
        }
 };
-static impl impl;
 
+static sound_resource_loader loader;
 
-class sound::impl
+
+
+// SOUND BUFFER
+
+class buffer
 {
 public:
 
-       static ALenum get_audio_format(const vorbis_info* audioInfo)
+       typedef hash<ALuint,int,hash_function> retcount_lookup;
+
+
+       buffer() :
+               buffer_((ALuint)-1) {}
+
+       buffer(const void* data,
+                  ALsizei size,
+                  ALenum format,
+                  ALsizei freq)
        {
-               if (audioInfo->channels == 1) return AL_FORMAT_MONO16;
-               else                                              return AL_FORMAT_STEREO16;
+               alGenBuffers(1, &buffer_);
+               alBufferData(buffer_, format, data, size, freq);
+
+               retain_counts_[buffer_] = 1;
+               log_warning("ctor buffer:", buffer_);
        }
-       
 
-       class buffer;
-       typedef boost::shared_ptr<buffer> buffer_ptr;
-       
-       class buffer : public manager<buffer>
+       buffer(const buffer& buf)
        {
-       public:
+               buffer_ = buf.buffer_;
+               retain();
+       }
 
-               buffer() :
-                       buffer_(-1)
+       buffer& operator = (const buffer& buf)
+       {
+               buffer_ = buf.buffer_;
+               retain();
+               return *this;
+       }
+
+       ~buffer()
+       {
+               release();
+       }
+
+
+       void queue(ALuint source) const
+       {
+               if (*this)
                {
-                       mOggStream.datasource = 0;
+                       alSourceQueueBuffers(source, 1, &buffer_);
+                       retain();
+                       log_warning("queued buffer:", buffer_);
                }
+       }
+
+       static buffer unqueue(ALuint source)
+       {
+               ALuint buf = (ALuint)-1;
+               alSourceUnqueueBuffers(source, 1, &buf);
+               log_warning("unqueued buffer:", buf);
+               return buffer(buf);
+       }
+
+       void set(ALuint source) const
+       {
+               log_warning("set buffer:", buffer_);
+               if (*this) alSourcei(source, AL_BUFFER, buffer_);
+       }
+
+       operator bool () const
+       {
+               return buffer_ != (ALuint)-1;
+       }
+
+
+private:
 
-               ~buffer()
+       explicit buffer(ALuint buf) :
+               buffer_(buf) {}
+
+
+       void retain() const
+       {
+               retcount_lookup::iterator it = retain_counts_.find(buffer_);
+               if (it.valid()) ++it->second;
+       }
+
+       void release() const
+       {
+               retcount_lookup::iterator it = retain_counts_.find(buffer_);
+               if (it.valid() && --it->second <= 0)
                {
-                       if (mOggStream.datasource)
-                       {
-                               ov_clear(&mOggStream);
-                       }
-                       if (int(buffer_) != -1) alDeleteBuffers(1, &buffer_);
+                       alDeleteBuffers(1, &buffer_);
+                       retain_counts_.erase(it);
+                       log_warning("kill buffer:", buffer_);
                }
+       }
 
 
-               void init(const std::string& path)
-               {
-                       log_info("initializing audio buffer...");
-                       if (mOggStream.datasource)
-                       {
-                               ov_clear(&mOggStream);
-                               mOggStream.datasource = 0;
-                       }
+       ALuint                                  buffer_;
+       sound_backend                   backend_;
 
-                       if (ov_fopen((char*)path.c_str(), &mOggStream) < 0)
-                       {
-                               throw std::runtime_error("problem reading audio: " + path);
-                       }
+       static retcount_lookup  retain_counts_;
+};
 
-                       vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
-                       mFormat = get_audio_format(vorbisInfo);
-                       mFreq = vorbisInfo->rate;
-               }
+buffer::retcount_lookup buffer::retain_counts_;
 
 
-               void load_all(ALuint source)
-               {
-                       if (!mOggStream.datasource) init(name());
-                       if (!mOggStream.datasource) return;
 
-                       char data[BUF_SIZE];
-                       int size = 0;
+// SOUND RESOURCE
 
-                       for (;;)
-                       {
-                               int section;
-                               int result = ov_read(&mOggStream, data + size,
-                                               BUF_SIZE - size, 0, 2, 1, &section);
+class sound_resource
+{
+public:
 
-                               if (result > 0)
-                               {
-                                       size += result;
-                               }
-                               else
-                               {
-                                       if (result < 0) log_warning("vorbis playback error");
-                                       break;
-                               }
-                       }
-                       if (size == 0)
-                       {
-                               log_warning("decoded no bytes from", name());
-                               return;
-                       }
+       sound_resource(const std::string& path)
+       {
+               log_info("audio path is", path);
+               if (ov_fopen((char*)path.c_str(), &file_) < 0)
+               {
+                       throw std::runtime_error("problem reading audio: " + path);
+               }
+       }
+
+       ~sound_resource()
+       {
+               ov_clear(&file_);
+       }
 
-                       alGenBuffers(1, &buffer_);
 
-                       alBufferData(buffer_, mFormat, data, size, mFreq);
-                       alSourcei(source, AL_BUFFER, buffer_);
+       bool read(buffer& buf)
+       {
+               if (buffer_)
+               {
+                       buf = buffer_;
+                       return true;
+               }
 
-                       // don't need to keep this loaded
-                       ov_clear(&mOggStream);
-                       mOggStream.datasource = 0;
+               if (ov_pcm_seek_lap(&file_, 0) != 0)
+               {
+                       log_warning("vorbis seek error");
+                       return false;
                }
 
-               bool stream(ALuint buffer)
+               char    data[64*BUF_SIZE];
+               size_t  size = 0;
+
+               while (size < sizeof(data))
                {
-                       char data[BUF_SIZE];
-                       int size = 0;
+                       int     section;
+                       int     result = ov_read(&file_,
+                                                                data + size, sizeof(data) - size,
+                                                                0, 2, 1, &section);
 
-                       while (size < BUF_SIZE)
+                       if (result > 0)
                        {
-                               int section;
-                               int result = ov_read(&mOggStream, data + size,
-                                               BUF_SIZE - size, 0, 2, 1, &section);
-
-                               if (result > 0)
-                               {
-                                       size += result;
-                               }
-                               else
-                               {
-                                       if (result < 0) log_warning("vorbis playback error");
-                                       break;
-                               }
+                               size += result;
+                               continue;
                        }
+                       else if (result == 0 && size > 0)
+                       {
+                               log_info("loaded", size, "bytes from vorbis");
+                               vorbis_info* info = ov_info(&file_, section);
+                               buffer_ = buffer(data, size,
+                                                                get_audio_format(info), info->rate);
+                               buf = buffer_;
+                               log_info("this section is", section);
+                               log_info("audio format is", get_audio_format(info));
+                               log_info("audio freq is", info->rate);
+                               return true;
+                       }
+                       else
+                       {
+                               log_warning("vorbis playback error");
+                               break;
+                       }
+               }
 
-                       if (size == 0) return false;
+               if (size >= sizeof(data)) log_warning("sample is too big to play");
+               return false;
+       }
 
-                       alBufferData(buffer, mFormat, data, size, mFreq);
 
-                       return true;
+       bool read(buffer& buf, uint64_t& sample)
+       {
+               if (ov_pcm_seek_lap(&file_, sample) != 0)
+               {
+                       log_warning("vorbis seek error");
+                       return false;
                }
 
-               void rewind()
+               char data[BUF_SIZE];
+               int     section;
+               int     result = ov_read(&file_, data, sizeof(data), 0, 2, 1, &section);
+
+               if (result > 0)
                {
-                       if (!mOggStream.datasource) init(name());
-                       else ov_raw_seek(&mOggStream, 0);
+                       log_info("loaded", result, "bytes from vorbis");
+                       vorbis_info* info = ov_info(&file_, section);
+                       buf = buffer(data, result, get_audio_format(info), info->rate);
+                       sample = ov_pcm_tell(&file_);
+                       log_info("this section is", section);
+                       log_info("next sample is", sample);
+                       log_info("audio format is", get_audio_format(info));
+                       log_info("audio freq is", info->rate);
+                       return true;
                }
 
+               if (result < 0) log_warning("vorbis playback error");
+               return false;
+       }
 
-       private:
 
-               OggVorbis_File  mOggStream;
-               ALenum                  mFormat;
-               ALsizei                 mFreq;
-               ALuint                  buffer_;
-       };
+       static ALenum get_audio_format(const vorbis_info* info)
+       {
+               if (info->channels == 1) return AL_FORMAT_MONO16;
+               else                                     return AL_FORMAT_STEREO16;
+       }
 
 
+private:
+
+       OggVorbis_File  file_;
+       buffer                  buffer_;
+};
+
+
+
+class sound::impl
+{
+public:
+
        impl()
        {
                init();
@@ -202,36 +368,26 @@ public:
 
        void init()
        {
-               retain_backend();
-
-               is_loaded_ = false;
                is_playing_ = false;
                is_looping_ = false;
 
+               sample_ = 0;
+
                alGenSources(1, &source_);
+               log_error("alGenSources:", alGetError());
 
                ALfloat zero[] = {0.0f, 0.0f, 0.0f};
                alSourcef(source_,  AL_PITCH, 1.0f);
                alSourcef(source_,  AL_GAIN, 1.0f);
                alSourcefv(source_, AL_POSITION, zero);
                alSourcefv(source_, AL_VELOCITY, zero);
-
-               alSourcei(source_, AL_LOOPING, is_looping_);
+               log_error("init:", alGetError());
        }
 
        ~impl()
        {
                stop();
-
                alDeleteSources(1, &source_);
-
-               while (!buffers_.empty())
-               {
-                       alDeleteBuffers(1, &buffers_.back());
-                       buffers_.pop_back();
-               }
-
-               release_backend();
        }
 
 
@@ -239,53 +395,60 @@ public:
        {
                if (queue_.empty()) return;
 
-               if (!is_loaded_) queue_.front()->load_all(source_);
+               sound_handle    handle = queue_.front();
+               buffer                  buf;
 
-               alSourcePlay(source_);
-               is_loaded_ = true;
+               if (handle->read(buf))
+               {
+                       log_info("playing source...");
+                       buf.set(source_);
+                       alSourcei(source_, AL_LOOPING, is_looping_);
+                       alSourcePlay(source_);
+               }
        }
 
-
-       void play_stream()
+       void stream()
        {
                if (queue_.empty()) return;
 
                if (!is_playing_)
                {
                        alSourcei(source_, AL_LOOPING, false);
-                       buffer_stream();
+                       log_error("set not looping:", alGetError());
+
+                       sound_handle handle = queue_.front();
+
+                       for (int i = 0; i < NUM_BUFFERS; ++i)
+                       {
+                               buffer buf;
+                               if (handle->read(buf, sample_))
+                               {
+                                       buf.queue(source_);
+                               }
+                               else
+                               {
+                                       log_error("failed to start stream");
+                                       break;
+                               }
+
+                               ALint queued = 0;
+                               alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
+                               log_info("buffers queued:", queued);
+                       }
                }
 
                if (!stream_timer_.is_valid())
                {
                        stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2),
-                                       1.0, timer::repeat);
+                                       0.01, timer::repeat);
                }
 
+               log_info("streaming source...");
                alSourcePlay(source_);
+               log_error("playing:", alGetError());
                is_playing_ = true;
        }
 
-       void buffer_stream()
-       {
-               ALuint buffer;
-               for (int i = buffers_.size(); i <= 8; ++i)
-               {
-                       alGenBuffers(1, &buffer);
-
-                       if (queue_.front()->stream(buffer))
-                       {
-                               alSourceQueueBuffers(source_, 1, &buffer);
-                               buffers_.push_back(buffer);
-                       }
-                       else
-                       {
-                               alDeleteBuffers(1, &buffer);
-                               break;
-                       }
-               }
-       }
-
 
        void update()
        {
@@ -295,47 +458,50 @@ public:
 
                while (finished-- > 0)
                {
-                       ALuint bufferObj;
-                       alSourceUnqueueBuffers(source_, 1, &bufferObj);
-
-                       buffer_ptr buffer = queue_.front();
-                       bool streamed = buffer->stream(bufferObj);
+                       buffer::unqueue(source_);
+                       bool streamed = false;
+                       //if (handle->is_loaded())
+                       //{
+                               //streamed = handle->read(buf);
+                       //}
+                       //else
+                       //{
+                               buffer buf;
+                               sound_handle handle = queue_.front();
+                               streamed = handle->read(buf, sample_);
+                       //}
 
                        if (streamed)
                        {
-                               alSourceQueueBuffers(source_, 1, &bufferObj);
+                               buf.queue(source_);
                        }
                        else
                        {
                                // the buffer couldn't be streamed, so get rid of it
                                queue_.pop_front();
+                               sample_ = 0;
 
                                if (!queue_.empty())
                                {
                                        // begin the next buffer in the queue
-                                       queue_.front()->rewind();
-                                       queue_.front()->stream(bufferObj);
-                                       alSourceQueueBuffers(source_, 1, &bufferObj);
+                                       handle->read(buf, sample_);
+                                       buf.queue(source_);
                                        log_info("loading new buffer");
-
-                                       // queue up any unused buffers
-                                       buffer_stream();
                                }
                                else if (is_looping_)
                                {
                                        // reload the same buffer
-                                       queue_.push_back(buffer);
-                                       buffer->rewind();
-                                       buffer->stream(bufferObj);
-                                       alSourceQueueBuffers(source_, 1, &bufferObj);
                                        log_info("looping same buffer");
+
+                                       queue_.push_back(handle);
+                                       handle->read(buf, sample_);
+                                       buf.queue(source_);
                                }
                                else
                                {
                                        // nothing more to play, stopping...
-                                       is_playing_ = false;
-                                       std::remove(buffers_.begin(), buffers_.end(),
-                                                               bufferObj);
+                                       stop();
+                                       queue_.push_back(handle);
                                }
                        }
                }
@@ -358,6 +524,8 @@ public:
                is_playing_ = false;
 
                stream_timer_.invalidate();
+
+               // TODO: clear buffers if streaming
        }
 
        void pause()
@@ -368,6 +536,12 @@ public:
                stream_timer_.invalidate();
        }
 
+       void rewind()
+       {
+               alSourceRewind(source_);
+               sample_ = 0;
+       }
+
 
        void sample(const std::string& path)
        {
@@ -375,32 +549,24 @@ public:
                alSourcei(source_, AL_BUFFER, AL_NONE);
 
                queue_.clear();
-               is_loaded_ = false;
 
                enqueue(path);
-
-               while (!buffers_.empty())
-               {
-                       alDeleteBuffers(1, &buffers_.back());
-                       buffers_.pop_back();
-               }
        }
 
        void enqueue(const std::string& path)
        {
-               buffer_ptr buffer = buffer::instance(path);
-               queue_.push_back(buffer);
+               sound_handle handle = resource::load(path);
+               queue_.push_back(handle);
        }
 
 
        bool is_playing() const
        {
-               if (is_playing_) return true;
-
                ALenum state;
                alGetSourcei(source_, AL_SOURCE_STATE, &state);
 
-               return state == AL_PLAYING;
+               if (state == AL_PLAYING) return true;
+               else                     return is_playing_;
        }
 
 
@@ -426,64 +592,23 @@ public:
        }
 
 
-       static void retain_backend()
-       {
-               if (retain_count_++ == 0)
-               {
-                       al_device_ = alcOpenDevice(0);
-                       al_context_ = alcCreateContext(al_device_, 0);
-                       if (!al_device_ || !al_context_)
-                       {
-                               const char* error = alcGetString(al_device_,
-                                                                                                alcGetError(al_device_));
-                               log_error("audio subsystem initialization failure", error);
-                       }
-                       else
-                       {
-                               alcMakeContextCurrent(al_context_);
-                               log_info << "opened sound device `"
-                                                << alcGetString(al_device_,
-                                                                                ALC_DEFAULT_DEVICE_SPECIFIER)
-                                                << "'" << std::endl;
-                       }
-               }
-       }
-
-       static void release_backend()
-       {
-               if (--retain_count_ == 0)
-               {
-                       alcMakeContextCurrent(0);
-                       alcDestroyContext(al_context_);
-                       alcCloseDevice(al_device_);
-               }
-       }
+       ALuint                                          source_;
 
+       bool                                            is_playing_;
+       bool                                            is_looping_;
 
-       ALuint                                  source_;
-       std::list<ALuint>               buffers_;
+       std::deque<sound_handle>        queue_;
+       uint64_t                                        sample_;
 
-       bool                                    is_loaded_;
-       bool                                    is_playing_;
-       bool                                    is_looping_;
+       timer                                           stream_timer_;
 
-       std::deque<buffer_ptr>  queue_;
-
-       timer                                   stream_timer_;
-
-       static unsigned                 retain_count_;
-       static ALCdevice*               al_device_;
-       static ALCcontext*              al_context_;
+       sound_backend                           backend_;
 };
 
-unsigned       sound::impl::retain_count_ = 0;
-ALCdevice*     sound::impl::al_device_ = 0;
-ALCcontext*    sound::impl::al_context_ = 0;
 
-
-//sound::sound() :
-       //// pass through
-       //impl_(new sound::impl) {}
+sound::sound() :
+       // pass through
+       impl_(new sound::impl) {}
 
 sound::sound(const std::string& path) :
        // pass through
@@ -499,6 +624,12 @@ void sound::sample(const std::string& path)
        impl_->sample(path);
 }
 
+void sound::enqueue(const std::string& path)
+{
+       // pass through
+       impl_->enqueue(path);
+}
+
 
 void sound::play()
 {
@@ -506,6 +637,12 @@ void sound::play()
        impl_->play();
 }
 
+void sound::stream()
+{
+       // pass through
+       impl_->stream();
+}
+
 void sound::stop()
 {
        // pass through
@@ -518,11 +655,18 @@ void sound::pause()
        impl_->pause();
 }
 
+void sound::rewind()
+{
+       // pass through
+       impl_->rewind();
+}
+
 
 void sound::toggle()
 {
        if (is_playing()) pause();
        else play();
+       // TODO: what about streaming sources?
 }
 
 bool sound::is_playing() const
@@ -587,22 +731,5 @@ void sound::listener_orientation(const vector3& forward,
 }
 
 
-//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
-void sound_stream::enqueue(const std::string& path)
-{
-       // pass through
-       impl_->enqueue(path);
-}
-
-
-void sound_stream::play()
-{
-       // pass through
-       impl_->play_stream();
-}
-
-
 } // namespace moof
 
This page took 0.04193 seconds and 4 git commands to generate.