X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fyoink;a=blobdiff_plain;f=src%2Fmoof%2Fsound.cc;h=bcd86670c302d4e6ff17d3bc0133eb73043cd9d9;hp=0714698673f02f8d7a757e0923ba39ca509f2191;hb=HEAD;hpb=ed04ddaaa59dcc42e375ec492dbda77f693530e9 diff --git a/src/moof/sound.cc b/src/moof/sound.cc index 0714698..bcd8667 100644 --- a/src/moof/sound.cc +++ b/src/moof/sound.cc @@ -1,19 +1,14 @@ -/*] Copyright (c) 2009-2010, Charles McGarvey [************************** +/*] Copyright (c) 2009-2011, Charles McGarvey [***************************** **] All rights reserved. * -* vi:ts=4 sw=4 tw=75 -* * Distributable under the terms and conditions of the 2-clause BSD license; * see the file COPYING for a complete text of the license. * -**************************************************************************/ +*****************************************************************************/ -#include #include -#include #include -#include #include #include @@ -23,24 +18,27 @@ #include #include -#include "hash.hh" -#include "log.hh" -#include "manager.hh" +#include "debug.hh" #include "sound.hh" #include "resource.hh" +#include "runloop.hh" +#include "thread.hh" #include "timer.hh" #ifndef BUF_SIZE -#define BUF_SIZE (4096) +#define BUF_SIZE 8192 #endif -#define NUM_BUFFERS (8) +#define NUM_SAMPLE_BITS 16 namespace moof { +thread stream_thread_; + + class sound_backend { public: @@ -54,27 +52,25 @@ public: if (!al_device || !al_context) { const char* error = alcGetString(al_device, - alcGetError(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; + 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) + sound_backend& operator = (const sound_backend& backend) { ++retain_count; return *this; @@ -90,13 +86,12 @@ public: } } - - static int retain_count; + static int retain_count; static ALCdevice* al_device; static ALCcontext* al_context; }; -int sound_backend::retain_count = 0; +int sound_backend::retain_count = 0; ALCdevice* sound_backend::al_device; ALCcontext* sound_backend::al_context; @@ -104,184 +99,63 @@ ALCcontext* sound_backend::al_context; class sound_resource; typedef resource_handle sound_handle; - -class sound_resource_loader -{ -public: - - sound_resource_loader() - { - resource::register_type("ogg", "sounds"); - } - - ~sound_resource_loader() - { - resource::unregister_type("ogg"); - } -}; - -static sound_resource_loader loader; - - - -// 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 +MOOF_REGISTER_RESOURCE(sound_resource, ogg, sounds); 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) - { - throw std::runtime_error("problem reading audio: " + path); - } + throw std::runtime_error("problem reading audio: " + + path); } ~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)) { int section; - int result = ov_read(&file_, - data + size, sizeof(data) - size, - 0, 2, 1, §ion); + int result = ov_read(&file_, 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 { @@ -289,50 +163,92 @@ public: break; } } - - 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) - { - log_warning("vorbis seek error"); + if ((sample == sample_ && ov_pcm_seek_lap(&file_, sample) != 0) || + (sample != sample_ && ov_pcm_seek(&file_, sample) != 0)) return false; - } - char data[BUF_SIZE]; + char data[BUF_SIZE]; int section; - int result = ov_read(&file_, data, sizeof(data), 0, 2, 1, §ion); + 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); - 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"); + } + 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 (result < 0) log_warning("vorbis playback error"); + 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; } + // 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_; +}; class sound::impl @@ -347,7 +263,7 @@ public: impl(const std::string& name) { init(); - enqueue(name); + sample(name); } void init() @@ -356,6 +272,7 @@ public: is_looping_ = false; sample_ = 0; + buffer_size_ = SCALAR(1.0); alGenSources(1, &source_); @@ -369,129 +286,196 @@ public: ~impl() { stop(); + deplete_stream(); + alDeleteSources(1, &source_); } - void play() + void deplete_stream() { - if (queue_.empty()) return; + if (is_streaming_) + { + 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); + } + } - sound_handle handle = queue_.front(); - buffer buf; + void play() + { + if (queue_.empty() || is_playing_) return; - if (handle->read(buf)) + ALenum state; + alGetSourcei(source_, AL_SOURCE_STATE, &state); + switch (state) { - buf.set(source_); - alSourcei(source_, AL_LOOPING, is_looping_); - alSourcePlay(source_); + case AL_INITIAL: + case AL_STOPPED: + 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; + + case AL_PAUSED: + if (is_streaming_) + { + start_update_timer(); + is_playing_ = true; + } + break; } + + alSourcePlay(source_); } - void stream() + void start_update_timer() { - if (queue_.empty()) return; + stream_timer_.init(boost::bind(&impl::stream_update, + this, _1, _2), + SCALAR(0.1), timer::repeat); - if (!is_playing_) - { - alSourcei(source_, AL_LOOPING, false); + thread::main_runloop().add_timer(stream_timer_); + + //thread thread; + //thread.runloop().add_timer(stream_timer_); + //if (!stream_thread_.is_valid()) + //stream_thread_ = thread::detach(stream_timer_); + // FIXME: proper locking needs to be done; this is causing crashes + // on shutdown when sound resources are destroyed yet the stream + // thread is neither shutdown or notified. + } - sound_handle handle = queue_.front(); + 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]; - for (int i = 0; i < NUM_BUFFERS; ++i) + if (0 < target_diff) { - buffer buf; - if (handle->read(buf, sample_)) + if (queue_buffer(buf)) { - buf.queue(source_); + --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_.mode() != timer::invalid && 0 < target_diff) { - stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2), - 0.01, 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() + // TODO: this function is ugly + 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()) + if (0 < queue_.size()) { - // begin the next buffer in the queue - 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 if (is_looping_) + else if (is_looping_ && !looped_once) { - // reload the same buffer - queue_.push_back(handle); - handle->read(buf, sample_); - buf.queue(source_); + queue_.push_back(sound); + looped_once = true; } else { - // nothing more to play, stopping... - stop(); - queue_.push_back(handle); + 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; } - void stop() { alSourceStop(source_); @@ -499,7 +483,7 @@ public: stream_timer_.invalidate(); - // TODO: clear buffers if streaming + sample_ = 0; } void pause() @@ -510,73 +494,62 @@ 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; } - bool is_playing() const { ALenum state; alGetSourcei(source_, AL_SOURCE_STATE, &state); if (state == AL_PLAYING) return true; - else return is_playing_; + else return is_playing_; } - void loop(bool looping) { is_looping_ = looping; ALenum type; alGetSourcei(source_, AL_SOURCE_TYPE, &type); - if (type != AL_STREAMING) - { alSourcei(source_, AL_LOOPING, is_looping_); - } } - void stream_update(timer& timer, scalar t) { - update(); - // TODO: might be nice to also allow using threads for streaming - // rather than a timer, probably as a compile-time option + log_debug("blaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhrrrrrg"); + fill_stream(); } + ALuint source_; - ALuint source_; - - bool is_playing_; - bool is_looping_; + bool is_playing_; + bool is_looping_; + bool is_streaming_; std::deque queue_; - uint64_t sample_; + uint64_t sample_; + int num_buffers_; + scalar buffer_size_; - timer stream_timer_; + timer stream_timer_; - sound_backend backend_; + sound_backend backend_; }; @@ -588,32 +561,24 @@ sound::sound(const std::string& path) : // pass through impl_(new sound::impl(path)) {} - void sound::sample(const std::string& path) { // pass through 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_->play(); } -void sound::stream() -{ - // pass through - impl_->stream(); -} - void sound::stop() { // pass through @@ -626,18 +591,10 @@ 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 @@ -646,6 +603,10 @@ 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) { @@ -675,7 +636,6 @@ void sound::loop(bool looping) impl_->loop(looping); } - void sound::listener_position(const vector3& position) { float vec[] = {position[0], position[1], position[2]}; @@ -689,7 +649,7 @@ void sound::listener_velocity(const vector3& velocity) } void sound::listener_orientation(const vector3& forward, - const vector3& up) + const vector3& up) { float vec[6]; vec[0] = float(forward[0]);