X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fyoink;a=blobdiff_plain;f=src%2Fmoof%2Fsound.cc;fp=src%2Fmoof%2Fsound.cc;h=f3060540eba005087560423b4dcdb0d167a524c6;hp=2c19d265b721f0b9d0e01a94f0bd93007157f948;hb=e9a16d767785b1a903a4466ff26265a5f7dae9e5;hpb=6f1b787a10d8ab1a3117a4b8c004dd2d90599608 diff --git a/src/moof/sound.cc b/src/moof/sound.cc index 2c19d26..f306054 100644 --- a/src/moof/sound.cc +++ b/src/moof/sound.cc @@ -9,9 +9,7 @@ * **************************************************************************/ -#include #include -#include #include #include @@ -22,7 +20,6 @@ #include #include -#include "hash.hh" #include "log.hh" #include "sound.hh" #include "resource.hh" @@ -30,10 +27,10 @@ #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_handle; MOOF_REGISTER_RESOURCE(sound_resource, ogg, sounds); -/*] Sound buffer - *************************************************************************/ - -class buffer -{ -public: - - typedef hash 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, §ion); - 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, §ion); - 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, §ion); + + 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 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) {