]> Dogcows Code - chaz/yoink/blobdiff - src/moof/sound.cc
testing improved runloop scheduling
[chaz/yoink] / src / moof / sound.cc
index 2c19d265b721f0b9d0e01a94f0bd93007157f948..f3060540eba005087560423b4dcdb0d167a524c6 100644 (file)
@@ -9,9 +9,7 @@
 *
 **************************************************************************/
 
-#include <cstdio>
 #include <deque>
-#include <list>
 #include <stdexcept>
 
 #include <boost/algorithm/string.hpp>
@@ -22,7 +20,6 @@
 #include <vorbis/codec.h>
 #include <vorbis/vorbisfile.h>
 
-#include "hash.hh"
 #include "log.hh"
 #include "sound.hh"
 #include "resource.hh"
 
 
 #ifndef BUF_SIZE
-#define BUF_SIZE       (4096 * 64)
+#define BUF_SIZE               (4096)
 #endif
 
-#define NUM_BUFFERS (4)
+#define NUM_SAMPLE_BITS        (16)
 
 
 namespace moof {
@@ -108,109 +105,6 @@ typedef resource_handle<sound_resource> sound_handle;
 MOOF_REGISTER_RESOURCE(sound_resource, ogg, sounds);
 
 
-/*] Sound buffer
- *************************************************************************/
-
-class buffer
-{
-public:
-
-       typedef hash<ALuint,int,hash_function> retcount_lookup;
-
-
-       buffer() :
-               buffer_((ALuint)-1) {}
-
-       buffer(const void* data,
-                  ALsizei size,
-                  ALenum format,
-                  ALsizei freq)
-       {
-               alGenBuffers(1, &buffer_);
-               alBufferData(buffer_, format, data, size, freq);
-
-               retain_counts_[buffer_] = 1;
-       }
-
-       buffer(const buffer& buf)
-       {
-               buffer_ = buf.buffer_;
-               retain();
-       }
-
-       buffer& operator = (const buffer& buf)
-       {
-               buffer_ = buf.buffer_;
-               retain();
-               return *this;
-       }
-
-       ~buffer()
-       {
-               release();
-       }
-
-
-       void queue(ALuint source) const
-       {
-               if (*this)
-               {
-                       alSourceQueueBuffers(source, 1, &buffer_);
-                       retain();
-               }
-       }
-
-       static buffer unqueue(ALuint source)
-       {
-               ALuint buf = (ALuint)-1;
-               alSourceUnqueueBuffers(source, 1, &buf);
-               return buffer(buf);
-       }
-
-       void set(ALuint source) const
-       {
-               if (*this) alSourcei(source, AL_BUFFER, buffer_);
-       }
-
-       operator bool () const
-       {
-               return buffer_ != (ALuint)-1;
-       }
-
-
-private:
-
-       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)
-               {
-                       alDeleteBuffers(1, &buffer_);
-                       retain_counts_.erase(it);
-               }
-       }
-
-
-       ALuint                                  buffer_;
-       sound_backend                   backend_;
-
-       static retcount_lookup  retain_counts_;
-};
-
-buffer::retcount_lookup buffer::retain_counts_;
-
-
-
 /*] Sound resource
  *************************************************************************/
 
@@ -218,7 +112,9 @@ class sound_resource : public boost::noncopyable
 {
 public:
 
-       sound_resource(const std::string& path)
+       sound_resource(const std::string& path) :
+               buffer_(AL_NONE),
+               sample_(0)
        {
                if (ov_fopen((char*)path.c_str(), &file_) < 0)
                {
@@ -229,24 +125,23 @@ public:
        ~sound_resource()
        {
                ov_clear(&file_);
+               if (buffer_) alDeleteBuffers(1, &buffer_);
        }
 
 
-       bool read(buffer& buf) const
+       ALuint read_all() const
        {
-               if (buffer_)
-               {
-                       buf = buffer_;
-                       return true;
-               }
+               if (buffer_) return buffer_;
 
-               if (ov_pcm_seek_lap(&file_, 0) != 0)
+               if (ov_pcm_seek(&file_, 0) != 0)
                {
                        log_warning("vorbis seek error");
-                       return false;
+                       return AL_NONE;
                }
 
-               char    data[64*BUF_SIZE];
+               ogg_int64_t samples = ov_pcm_total(&file_, 0);
+
+               char    data[2 * samples * (NUM_SAMPLE_BITS / 8)];
                size_t  size = 0;
 
                while (size < sizeof(data))
@@ -256,18 +151,21 @@ public:
                                                                 data + size, sizeof(data) - size,
                                                                 0, 2, 1, &section);
 
-                       if (result > 0)
+                       if (0 < result)
                        {
                                size += result;
-                               continue;
                        }
                        else if (result == 0 && size > 0)
                        {
                                vorbis_info* info = ov_info(&file_, section);
-                               buffer_ = buffer(data, size,
-                                                                get_audio_format(info), info->rate);
-                               buf = buffer_;
-                               return true;
+
+                               ALuint buffer;
+                               alGenBuffers(1, &buffer);
+                               alBufferData(buffer, get_audio_format(info),
+                                                        data, size, info->rate);
+
+                               buffer_ = buffer;
+                               return buffer;
                        }
                        else
                        {
@@ -276,16 +174,14 @@ public:
                        }
                }
 
-               if (size >= sizeof(data)) log_warning("sample is too big to play");
-               return false;
+               return AL_NONE;
        }
 
-
-       bool read(buffer& buf, uint64_t& sample) const
+       bool read(ALuint buffer, uint64_t& sample) const
        {
-               if (ov_pcm_seek_lap(&file_, sample) != 0)
+               if ((sample == sample_ && ov_pcm_seek_lap(&file_, sample) != 0) ||
+                       (sample != sample_ && ov_pcm_seek(&file_, sample) != 0))
                {
-                       log_warning("vorbis seek error");
                        return false;
                }
 
@@ -293,30 +189,73 @@ public:
                int     section;
                int     result = ov_read(&file_, data, sizeof(data), 0, 2, 1, &section);
 
-               if (result > 0)
+               if (0 < result)
+               {
+                       vorbis_info* info = ov_info(&file_, section);
+                       alBufferData(buffer, get_audio_format(info),
+                                                data, result, info->rate);
+                       sample_ = sample = ov_pcm_tell(&file_);
+                       return true;
+               }
+               else if (result < 0) log_warning("vorbis playback error");
+
+               return false;
+       }
+
+       bool read(ALuint buffer, uint64_t& sample, sound_handle other) const
+       {
+               ov_crosslap(&other->file_, &file_);
+
+               char data[BUF_SIZE];
+               int     section;
+               int     result = ov_read(&file_, data, sizeof(data), 0, 2, 1, &section);
+
+               if (0 < result)
                {
                        vorbis_info* info = ov_info(&file_, section);
-                       buf = buffer(data, result, get_audio_format(info), info->rate);
-                       sample = ov_pcm_tell(&file_);
+                       alBufferData(buffer, get_audio_format(info),
+                                                data, result, info->rate);
+                       sample_ = sample = ov_pcm_tell(&file_);
                        return true;
                }
+               else if (result < 0) log_warning("vorbis playback error");
 
-               if (result < 0) log_warning("vorbis playback error");
                return false;
        }
 
 
+       // determines the correct number of fixed-size buffers needed to hold a
+       // given number of seconds of PCM audio.
+       int num_buffers(scalar seconds) const
+       {
+               vorbis_info* info = ov_info(&file_, -1);
+               int count = scalar(info->rate) *                // sample rate
+                                       scalar(info->channels) *        // channels
+                                       scalar(NUM_SAMPLE_BITS) *       // sample size
+                                       seconds *                                       // time
+                                       SCALAR(0.125) /                         // bits to bytes
+                                       scalar(BUF_SIZE);                       // individual buffer size
+               return count;
+       }
+
+
        static ALenum get_audio_format(const vorbis_info* info)
        {
-               if (info->channels == 1) return AL_FORMAT_MONO16;
-               else                                     return AL_FORMAT_STEREO16;
+               if (info->channels == 1)      return AL_FORMAT_MONO16;
+               else if (info->channels == 2) return AL_FORMAT_STEREO16;
+
+               log_error("unsupported number of channels:", info->channels);
+               return 0;
        }
 
 
 private:
 
        mutable OggVorbis_File  file_;
-       mutable buffer                  buffer_;
+       mutable ALuint                  buffer_;
+       mutable uint64_t                sample_;
+
+       sound_backend                   backend_;
 };
 
 
@@ -335,7 +274,7 @@ public:
        impl(const std::string& name)
        {
                init();
-               enqueue(name);
+               sample(name);
        }
 
        void init()
@@ -344,6 +283,7 @@ public:
                is_looping_ = false;
 
                sample_ = 0;
+               buffer_size_ = SCALAR(1.0);
 
                alGenSources(1, &source_);
 
@@ -357,126 +297,175 @@ public:
        ~impl()
        {
                stop();
+               deplete_stream();
+
                alDeleteSources(1, &source_);
        }
 
 
-       void play()
+       void deplete_stream()
        {
-               if (queue_.empty()) return;
-
-               sound_handle    handle = queue_.front();
-               buffer                  buf;
-
-               if (handle->read(buf))
+               if (is_streaming_)
                {
-                       buf.set(source_);
-                       alSourcei(source_, AL_LOOPING, is_looping_);
-                       alSourcePlay(source_);
+                       ALint queued = 0;
+                       alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
+                       ALuint buffers[queued];
+                       alSourceUnqueueBuffers(source_, queued, buffers);
+                       alDeleteBuffers(queued, buffers);
                }
+               else alSourcei(source_, AL_BUFFER, AL_NONE);
        }
 
-       void stream()
+       void play()
        {
-               if (queue_.empty()) return;
+               if (queue_.empty() || is_playing_) return;
 
-               if (!is_playing_)
+               ALenum state;
+               alGetSourcei(source_, AL_SOURCE_STATE, &state);
+               switch (state)
                {
-                       alSourcei(source_, AL_LOOPING, false);
+                       case AL_INITIAL:
+                       case AL_STOPPED:
 
-                       sound_handle handle = queue_.front();
+                               if (is_streaming_)
+                               {
+                                       start_update_timer();
+                                       num_buffers_ = queue_.front()->num_buffers(buffer_size_);
+                                       fill_stream();
+                                       is_playing_ = true;
+                                       alSourcei(source_, AL_LOOPING, false);
+                               }
+                               else
+                               {
+                                       ALuint buffer = queue_.front()->read_all();
+                                       alSourcei(source_, AL_BUFFER, buffer);
+                                       alSourcei(source_, AL_LOOPING, is_looping_);
+                               }
+                               break;
 
-                       for (int i = 0; i < NUM_BUFFERS; ++i)
-                       {
-                               buffer buf;
-                               if (handle->read(buf, sample_))
+                       case AL_PAUSED:
+                               
+                               if (is_streaming_)
                                {
-                                       buf.queue(source_);
+                                       start_update_timer();
+                                       is_playing_ = true;
                                }
+                               break;
+               }
+
+               alSourcePlay(source_);
+       }
+
+       void start_update_timer()
+       {
+               stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2),
+                               SCALAR(0.1), timer::repeat);
+       }
+
+       void fill_stream()
+       {
+               ALenum type;
+               alGetSourcei(source_, AL_SOURCE_TYPE, &type);
+               ASSERT(type != AL_STATIC && "source shouldn't be static");
+               
+               ALint   processed = 0;
+               ALint   queued = 0;
+               alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
+               alGetSourcei(source_, AL_BUFFERS_PROCESSED, &processed);
+
+               int             target_diff = num_buffers_ - queued + processed;
+
+               ALuint  bufs[processed];
+               alSourceUnqueueBuffers(source_, processed, bufs);
+               for (int i = 0; i < processed; ++i)
+               {
+                       ALuint& buf = bufs[i];
+
+                       if (0 < target_diff)
+                       {
+                               if (queue_buffer(buf)) --target_diff;
                                else
                                {
-                                       log_error("failed to start stream");
+                                       alDeleteBuffers(processed - i - 1, &bufs[i+1]);
                                        break;
                                }
-
-                               ALint queued = 0;
-                               alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
                        }
+                       else alDeleteBuffers(1, &buf);
                }
 
-               if (!stream_timer_.is_valid())
+               if (stream_timer_.is_valid() && 0 < target_diff)
                {
-                       stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2),
-                                       SCALAR(0.5), timer::repeat);
+                       for (int i = 0; i < target_diff; ++i)
+                       {
+                               if (!queue_buffer(AL_NONE)) break;
+                       }
                }
 
-               alSourcePlay(source_);
-               is_playing_ = true;
+               if (is_playing_)
+               {
+                       ALenum state;
+                       alGetSourcei(source_, AL_SOURCE_STATE, &state);
+                       if (state != AL_PLAYING)
+                       {
+                               // restart playing if we're stopped but supposed to be
+                               // playing...  this means we didn't queue enough...
+                               alSourcePlay(source_);
+                               log_warning("not enough audio buffered");
+                       }
+               }
        }
 
-
-       void update()
+       bool queue_buffer(ALuint buffer)
        {
-               ALint finished = 0;
-
-               alGetSourcei(source_, AL_BUFFERS_PROCESSED, &finished);
+               if (buffer == AL_NONE) alGenBuffers(1, &buffer);
 
-               while (finished-- > 0)
+               bool looped_once = false;
+               
+               while (0 < queue_.size())
                {
-                       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)
+                       sound_handle sound = queue_.front();
+
+                       bool result = sound->read(buffer, sample_);
+                       if (result)
                        {
-                               buf.queue(source_);
+                               alSourceQueueBuffers(source_, 1, &buffer);
+                               return true;
                        }
                        else
                        {
-                               // the buffer couldn't be streamed, so get rid of it
-                               queue_.pop_front();
                                sample_ = 0;
+                               queue_.pop_front();
 
-                               if (!queue_.empty())
-                               {
-                                       // begin the next buffer in the queue
-                                       handle->read(buf, sample_);
-                                       buf.queue(source_);
-                               }
-                               else if (is_looping_)
+                               if (0 < queue_.size())
                                {
-                                       // reload the same buffer
-                                       queue_.push_back(handle);
-                                       handle->read(buf, sample_);
-                                       buf.queue(source_);
+                                       sound_handle new_sound = queue_.front();
+                                       result = new_sound->read(buffer, sample_, sound);
+                                       if (result)
+                                       {
+                                               queue_.push_back(new_sound);
+                                               alSourceQueueBuffers(source_, 1, &buffer);
+                                               num_buffers_ = new_sound->num_buffers(buffer_size_);
+                                               return true;
+                                       }
+                                       else
+                                       {
+                                               queue_.pop_front();
+                                               queue_.push_front(sound);
+                                       }
                                }
-                               else
+                               else if (is_looping_ && !looped_once)
                                {
-                                       // nothing more to play, stopping...
-                                       stop();
-                                       queue_.push_back(handle);
+                                       queue_.push_back(sound);
+                                       looped_once = true;
                                }
+                               else break;
                        }
                }
 
-               ALenum state;
-               alGetSourcei(source_, AL_SOURCE_STATE, &state);
-
-               // restart playing if we're stopped but supposed to be playing...
-               // this means we didn't queue enough and the audio skipped :-(
-               if (is_playing_ && state != AL_PLAYING)
-               {
-                       alSourcePlay(source_);
-               }
+               alDeleteBuffers(1, &buffer);
+               is_playing_ = false;
+               stream_timer_.invalidate();
+               return false;
        }
 
 
@@ -487,7 +476,7 @@ public:
 
                stream_timer_.invalidate();
 
-               // TODO: clear buffers if streaming
+               sample_ = 0;
        }
 
        void pause()
@@ -498,27 +487,22 @@ public:
                stream_timer_.invalidate();
        }
 
-       void rewind()
-       {
-               alSourceRewind(source_);
-               sample_ = 0;
-       }
-
 
        void sample(const std::string& name)
        {
                stop();
-               alSourcei(source_, AL_BUFFER, AL_NONE);
+               deplete_stream();
 
                queue_.clear();
-
-               enqueue(name);
+               queue(name);
+               is_streaming_ = false;
        }
 
-       void enqueue(const std::string& name)
+       void queue(const std::string& name)
        {
                sound_handle handle = resource::load(name, "ogg");
-               queue_.push_back(handle);
+               if (handle) queue_.push_back(handle);
+               is_streaming_ = true;
        }
 
 
@@ -538,7 +522,6 @@ public:
 
                ALenum type;
                alGetSourcei(source_, AL_SOURCE_TYPE, &type);
-
                if (type != AL_STREAMING)
                {
                        alSourcei(source_, AL_LOOPING, is_looping_);
@@ -548,7 +531,8 @@ public:
 
        void stream_update(timer& timer, scalar t)
        {
-               update();
+               //log_error("blaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhrrrrrg");
+               fill_stream();
                // TODO: might be nice to also allow using threads for streaming
                // rather than a timer, probably as a compile-time option
        }
@@ -558,9 +542,12 @@ public:
 
        bool                                            is_playing_;
        bool                                            is_looping_;
+       bool                                            is_streaming_;
 
        std::deque<sound_handle>        queue_;
        uint64_t                                        sample_;
+       int                                                     num_buffers_;
+       scalar                                          buffer_size_;
 
        timer                                           stream_timer_;
 
@@ -583,17 +570,17 @@ void sound::sample(const std::string& path)
        impl_->sample(path);
 }
 
-void sound::enqueue(const std::string& path)
+void sound::queue(const std::string& path)
 {
        // pass through
-       impl_->enqueue(path);
+       impl_->queue(path);
 }
 
 
 void sound::play()
 {
        // pass through
-       impl_->stream();
+       impl_->play();
 }
 
 void sound::stop()
@@ -608,13 +595,6 @@ void sound::pause()
        impl_->pause();
 }
 
-void sound::rewind()
-{
-       // pass through
-       impl_->rewind();
-}
-
-
 void sound::toggle()
 {
        if (is_playing()) pause();
@@ -627,6 +607,11 @@ bool sound::is_playing() const
        return impl_->is_playing();
 }
 
+void sound::buffer_size(scalar seconds)
+{
+       impl_->buffer_size_ = seconds;
+}
+
 
 void sound::position(const vector3& position)
 {
This page took 0.033014 seconds and 4 git commands to generate.