X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fyoink;a=blobdiff_plain;f=src%2Fmoof%2Fsound.cc;h=bcd86670c302d4e6ff17d3bc0133eb73043cd9d9;hp=d5b67bd2d61d85de1d64d0439bc04bd259bcb990;hb=44b3014bce798789e795242d1556cb7449e6386a;hpb=6b0a0d0efafe34d48ab344fca3b479553bd4e62c diff --git a/src/moof/sound.cc b/src/moof/sound.cc index d5b67bd..bcd8667 100644 --- a/src/moof/sound.cc +++ b/src/moof/sound.cc @@ -1,217 +1,279 @@ -/*] 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 +#include #include #include #include #include -#include "log.hh" -#include "manager.hh" +#include "debug.hh" #include "sound.hh" +#include "resource.hh" +#include "runloop.hh" +#include "thread.hh" #include "timer.hh" -#define BUF_SIZE (64 * 1024) -//#define BUF_SIZE (5*2048) + +#ifndef BUF_SIZE +#define BUF_SIZE 8192 +#endif + +#define NUM_SAMPLE_BITS 16 + namespace moof { -class impl +thread stream_thread_; + + +class sound_backend { public: - - impl() + + 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) { - //log_info("registering ogg resource handler"); - resource::register_type("ogg"); + ++retain_count; } - ~impl() + sound_backend& operator = (const sound_backend& backend) + { + ++retain_count; + return *this; + } + + ~sound_backend() { - //log_info("unregistering ogg resource handler"); - resource::unregister_type("ogg"); + if (--retain_count == 0) + { + alcMakeContextCurrent(0); + alcDestroyContext(al_context); + alcCloseDevice(al_device); + } } + + static int retain_count; + static ALCdevice* al_device; + static ALCcontext* al_context; }; -static impl impl; +int sound_backend::retain_count = 0; +ALCdevice* sound_backend::al_device; +ALCcontext* sound_backend::al_context; -class sound::impl +class sound_resource; +typedef resource_handle sound_handle; + +MOOF_REGISTER_RESOURCE(sound_resource, ogg, sounds); + +class sound_resource : public boost::noncopyable { public: - static ALenum get_audio_format(const vorbis_info* audioInfo) + sound_resource(const std::string& path) : + buffer_(AL_NONE), + sample_(0) { - if (audioInfo->channels == 1) return AL_FORMAT_MONO16; - else return AL_FORMAT_STEREO16; + if (ov_fopen((char*)path.c_str(), &file_) < 0) + throw std::runtime_error("problem reading audio: " + + path); } - - class buffer; - typedef boost::shared_ptr buffer_ptr; - - class buffer : public manager + ~sound_resource() { - public: + ov_clear(&file_); + if (buffer_) alDeleteBuffers(1, &buffer_); + } - buffer() : - buffer_(-1) - { - mOggStream.datasource = 0; - } + ALuint read_all() const + { + if (buffer_) return buffer_; - ~buffer() + if (ov_pcm_seek(&file_, 0) != 0) { - if (mOggStream.datasource) - { - ov_clear(&mOggStream); - } - if (int(buffer_) != -1) alDeleteBuffers(1, &buffer_); + log_warning("vorbis seek error"); + return AL_NONE; } + ogg_int64_t samples = ov_pcm_total(&file_, 0); + + char data[2 * samples * (NUM_SAMPLE_BITS / 8)]; + size_t size = 0; - void init(const std::string& path) + while (size < sizeof(data)) { - log_info("initializing audio buffer..."); - if (mOggStream.datasource) - { - ov_clear(&mOggStream); - mOggStream.datasource = 0; - } + int section; + int result = ov_read(&file_, data + size, + sizeof(data) - size, 0, 2, 1, §ion); - if (ov_fopen((char*)path.c_str(), &mOggStream) < 0) + if (0 < result) { - throw std::runtime_error("problem reading audio: " + path); + size += result; } - - vorbis_info* vorbisInfo = ov_info(&mOggStream, -1); - mFormat = get_audio_format(vorbisInfo); - mFreq = vorbisInfo->rate; - } - - - void load_all(ALuint source) - { - if (!mOggStream.datasource) init(name()); - if (!mOggStream.datasource) return; - - char data[BUF_SIZE]; - int size = 0; - - for (;;) + else if (result == 0 && size > 0) { - int section; - int result = ov_read(&mOggStream, data + size, - BUF_SIZE - size, 0, 2, 1, §ion); + vorbis_info* info = ov_info(&file_, section); - if (result > 0) - { - size += result; - } - else - { - if (result < 0) log_warning("vorbis playback error"); - break; - } + ALuint buffer; + alGenBuffers(1, &buffer); + alBufferData(buffer, get_audio_format(info), + data, size, info->rate); + + buffer_ = buffer; + return buffer; } - if (size == 0) + else { - log_warning("decoded no bytes from", name()); - return; + log_warning("vorbis playback error"); + break; } + } + return AL_NONE; + } - alGenBuffers(1, &buffer_); + bool read(ALuint buffer, uint64_t& sample) const + { + if ((sample == sample_ && ov_pcm_seek_lap(&file_, sample) != 0) || + (sample != sample_ && ov_pcm_seek(&file_, sample) != 0)) + return false; - alBufferData(buffer_, mFormat, data, size, mFreq); - alSourcei(source, AL_BUFFER, buffer_); + char data[BUF_SIZE]; + int section; + int result = ov_read(&file_, data, sizeof(data), + 0, 2, 1, §ion); - // don't need to keep this loaded - ov_clear(&mOggStream); - mOggStream.datasource = 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; } - - bool stream(ALuint buffer) + else if (result < 0) { - char data[BUF_SIZE]; - int size = 0; - - while (size < BUF_SIZE) - { - int section; - int result = ov_read(&mOggStream, data + size, - BUF_SIZE - size, 0, 2, 1, §ion); - - if (result > 0) - { - size += result; - } - else - { - if (result < 0) log_warning("vorbis playback error"); - break; - } - } + log_warning("vorbis playback error"); + } + return false; + } - if (size == 0) return false; + bool read(ALuint buffer, uint64_t& sample, sound_handle other) const + { + ov_crosslap(&other->file_, &file_); - alBufferData(buffer, mFormat, data, size, mFreq); + 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); + alBufferData(buffer, get_audio_format(info), + data, result, info->rate); + sample_ = sample = ov_pcm_tell(&file_); return true; } - - void rewind() + else if (result < 0) { - if (!mOggStream.datasource) init(name()); - else ov_raw_seek(&mOggStream, 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; + } - private: + static ALenum get_audio_format(const vorbis_info* info) + { + if (info->channels == 1) + return AL_FORMAT_MONO16; + else if (info->channels == 2) + return AL_FORMAT_STEREO16; - OggVorbis_File mOggStream; - ALenum mFormat; - ALsizei mFreq; - ALuint buffer_; - }; + log_error("unsupported number of channels:", info->channels); + return 0; + } +private: + + mutable OggVorbis_File file_; + mutable ALuint buffer_; + mutable uint64_t sample_; + + sound_backend backend_; +}; + + +class sound::impl +{ +public: impl() { init(); } - impl(const std::string& path) + impl(const std::string& name) { - log_info("sound::impl constructor"); init(); - enqueue(path); + sample(name); } void init() { - retain_backend(); - - is_loaded_ = false; is_playing_ = false; is_looping_ = false; + sample_ = 0; + buffer_size_ = SCALAR(1.0); + alGenSources(1, &source_); ALfloat zero[] = {0.0f, 0.0f, 0.0f}; @@ -219,149 +281,209 @@ public: alSourcef(source_, AL_GAIN, 1.0f); alSourcefv(source_, AL_POSITION, zero); alSourcefv(source_, AL_VELOCITY, zero); - - alSourcei(source_, AL_LOOPING, is_looping_); } ~impl() { stop(); + deplete_stream(); alDeleteSources(1, &source_); + } + - while (!buffers_.empty()) + void deplete_stream() + { + if (is_streaming_) { - alDeleteBuffers(1, &buffers_.back()); - buffers_.pop_back(); + 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); } - - release_backend(); } - void play() { - if (queue_.empty()) return; + if (queue_.empty() || is_playing_) return; - if (!is_loaded_) queue_.front()->load_all(source_); + ALenum state; + alGetSourcei(source_, AL_SOURCE_STATE, &state); + switch (state) + { + 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_); - is_loaded_ = true; } - - void play_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); + + 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. + } - if (!is_playing_) + 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) { - alSourcei(source_, AL_LOOPING, false); - buffer_stream(); - } + ALuint& buf = bufs[i]; - if (!stream_timer_.is_valid()) - { - stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2), - 1.0, timer::repeat); + if (0 < target_diff) + { + if (queue_buffer(buf)) + { + --target_diff; + } + else + { + alDeleteBuffers(processed - i - 1, &bufs[i+1]); + break; + } + } + else alDeleteBuffers(1, &buf); } - alSourcePlay(source_); - is_playing_ = true; - } - - void buffer_stream() - { - ALuint buffer; - for (int i = buffers_.size(); i <= 8; ++i) + if (stream_timer_.mode() != timer::invalid && 0 < target_diff) { - alGenBuffers(1, &buffer); - - if (queue_.front()->stream(buffer)) + for (int i = 0; i < target_diff; ++i) { - alSourceQueueBuffers(source_, 1, &buffer); - buffers_.push_back(buffer); + if (!queue_buffer(AL_NONE)) break; } - else + } + + if (is_playing_) + { + ALenum state; + alGetSourcei(source_, AL_SOURCE_STATE, &state); + if (state != AL_PLAYING) { - alDeleteBuffers(1, &buffer); - break; + // 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()) { - ALuint bufferObj; - alSourceUnqueueBuffers(source_, 1, &bufferObj); + sound_handle sound = queue_.front(); - buffer_ptr buffer = queue_.front(); - bool streamed = buffer->stream(bufferObj); - - if (streamed) + bool result = sound->read(buffer, sample_); + if (result) { - alSourceQueueBuffers(source_, 1, &bufferObj); + alSourceQueueBuffers(source_, 1, &buffer); + return true; } else { - // the buffer couldn't be streamed, so get rid of it + sample_ = 0; queue_.pop_front(); - if (!queue_.empty()) + if (0 < queue_.size()) { - // begin the next buffer in the queue - queue_.front()->rewind(); - queue_.front()->stream(bufferObj); - alSourceQueueBuffers(source_, 1, &bufferObj); - log_info("loading new buffer"); - - // queue up any unused buffers - buffer_stream(); + 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(buffer); - buffer->rewind(); - buffer->stream(bufferObj); - alSourceQueueBuffers(source_, 1, &bufferObj); - log_info("looping same buffer"); + queue_.push_back(sound); + looped_once = true; } else { - // nothing more to play, stopping... - is_playing_ = false; - std::remove(buffers_.begin(), buffers_.end(), - bufferObj); + 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_); is_playing_ = false; stream_timer_.invalidate(); + + sample_ = 0; } void pause() @@ -372,130 +494,72 @@ public: stream_timer_.invalidate(); } - - void sample(const std::string& path) + void sample(const std::string& name) { stop(); - alSourcei(source_, AL_BUFFER, AL_NONE); + deplete_stream(); queue_.clear(); - is_loaded_ = false; - - enqueue(path); - - while (!buffers_.empty()) - { - alDeleteBuffers(1, &buffers_.back()); - buffers_.pop_back(); - } + queue(name); + is_streaming_ = false; } - void enqueue(const std::string& path) + void queue(const std::string& name) { - buffer_ptr buffer = buffer::instance(path); - queue_.push_back(buffer); + sound_handle handle = resource::load(name, "ogg"); + if (handle) queue_.push_back(handle); + is_streaming_ = true; } - 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_; } - 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 - } - - - 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_); - } + log_debug("blaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhrrrrrg"); + fill_stream(); } + ALuint source_; - ALuint source_; - std::list buffers_; + bool is_playing_; + bool is_looping_; + bool is_streaming_; - bool is_loaded_; - bool is_playing_; - bool is_looping_; + std::deque queue_; + uint64_t sample_; + int num_buffers_; + scalar buffer_size_; - std::deque queue_; + timer stream_timer_; - 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 - impl_(new sound::impl(path)) -{ - log_info("sound constructor"); -} - + impl_(new sound::impl(path)) {} void sound::sample(const std::string& path) { @@ -503,6 +567,11 @@ void sound::sample(const std::string& path) impl_->sample(path); } +void sound::queue(const std::string& path) +{ + // pass through + impl_->queue(path); +} void sound::play() { @@ -522,7 +591,6 @@ void sound::pause() impl_->pause(); } - void sound::toggle() { if (is_playing()) pause(); @@ -535,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) { @@ -564,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]}; @@ -578,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]); @@ -591,22 +662,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