X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fyoink;a=blobdiff_plain;f=src%2Fmoof%2Fsound.cc;h=864c68abda05f3820f823e8a81922fb2a9b909ea;hp=d5b67bd2d61d85de1d64d0439bc04bd259bcb990;hb=6c9943707d4f33035830eba0587a61a34eaecbc2;hpb=6b0a0d0efafe34d48ab344fca3b479553bd4e62c diff --git a/src/moof/sound.cc b/src/moof/sound.cc index d5b67bd..864c68a 100644 --- a/src/moof/sound.cc +++ b/src/moof/sound.cc @@ -9,209 +9,287 @@ * **************************************************************************/ -#include #include -#include #include -#include #include - +#include +#include #include #include #include #include #include "log.hh" -#include "manager.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 (4096) +#endif + +#define NUM_SAMPLE_BITS (16) + namespace moof { -class impl +thread stream_thread_; + + +/*] Sound backend + *************************************************************************/ + +class sound_backend { public: - - impl() + + sound_backend() { - //log_info("registering ogg resource handler"); - resource::register_type("ogg"); + 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; + } + } } - ~impl() + + sound_backend(const sound_backend& backend) + { + ++retain_count; + } + + sound_backend& operator=(const sound_backend& backend) { - //log_info("unregistering ogg resource handler"); - resource::unregister_type("ogg"); + ++retain_count; + return *this; } + + ~sound_backend() + { + 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); + + +/*] Sound resource + *************************************************************************/ + +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 (ov_fopen((char*)path.c_str(), &file_) < 0) + { + throw std::runtime_error("problem reading audio: " + path); + } + } + + ~sound_resource() { - if (audioInfo->channels == 1) return AL_FORMAT_MONO16; - else return AL_FORMAT_STEREO16; + ov_clear(&file_); + if (buffer_) alDeleteBuffers(1, &buffer_); } - - class buffer; - typedef boost::shared_ptr buffer_ptr; - - class buffer : public manager + + ALuint read_all() const { - public: + if (buffer_) return buffer_; - buffer() : - buffer_(-1) + if (ov_pcm_seek(&file_, 0) != 0) { - mOggStream.datasource = 0; + log_warning("vorbis seek error"); + return AL_NONE; } - ~buffer() - { - if (mOggStream.datasource) - { - ov_clear(&mOggStream); - } - if (int(buffer_) != -1) alDeleteBuffers(1, &buffer_); - } + 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) + int section; + int result = ov_read(&file_, + data + size, sizeof(data) - size, + 0, 2, 1, §ion); + + if (0 < result) { - ov_clear(&mOggStream); - mOggStream.datasource = 0; + size += result; } + else if (result == 0 && size > 0) + { + vorbis_info* info = ov_info(&file_, section); - if (ov_fopen((char*)path.c_str(), &mOggStream) < 0) + ALuint buffer; + alGenBuffers(1, &buffer); + alBufferData(buffer, get_audio_format(info), + data, size, info->rate); + + buffer_ = buffer; + return buffer; + } + else { - throw std::runtime_error("problem reading audio: " + path); + log_warning("vorbis playback error"); + break; } - - vorbis_info* vorbisInfo = ov_info(&mOggStream, -1); - mFormat = get_audio_format(vorbisInfo); - mFreq = vorbisInfo->rate; } + return AL_NONE; + } - void load_all(ALuint source) + 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)) { - if (!mOggStream.datasource) init(name()); - if (!mOggStream.datasource) return; + return false; + } - char data[BUF_SIZE]; - int size = 0; + char data[BUF_SIZE]; + int section; + int result = ov_read(&file_, data, sizeof(data), 0, 2, 1, §ion); - for (;;) - { - int section; - int result = ov_read(&mOggStream, data + size, - BUF_SIZE - size, 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; + } + else if (result < 0) log_warning("vorbis playback error"); - 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; - } + return false; + } - alGenBuffers(1, &buffer_); + bool read(ALuint buffer, uint64_t& sample, sound_handle other) const + { + ov_crosslap(&other->file_, &file_); - 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; } + else if (result < 0) log_warning("vorbis playback error"); - bool stream(ALuint buffer) - { - char data[BUF_SIZE]; - int size = 0; + return false; + } - 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; - } - } + // 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; + } - if (size == 0) return false; - alBufferData(buffer, mFormat, data, size, mFreq); + 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; - return true; - } + log_error("unsupported number of channels:", info->channels); + return 0; + } - void rewind() - { - if (!mOggStream.datasource) init(name()); - else ov_raw_seek(&mOggStream, 0); - } +private: + + mutable OggVorbis_File file_; + mutable ALuint buffer_; + mutable uint64_t sample_; + + sound_backend backend_; +}; - private: - OggVorbis_File mOggStream; - ALenum mFormat; - ALsizei mFreq; - ALuint buffer_; - }; +/*] Sound class + *************************************************************************/ +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,140 +297,188 @@ 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); } - - release_backend(); + else alSourcei(source_, AL_BUFFER, AL_NONE); } - 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); - if (!is_playing_) + //thread thread; + //thread.runloop().add_timer(stream_timer_); + if (!stream_thread_.is_valid()) { - alSourcei(source_, AL_LOOPING, false); - buffer_stream(); + stream_thread_ = thread::detach(stream_timer_); } - - if (!stream_timer_.is_valid()) - { - stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2), - 1.0, timer::repeat); - } - - alSourcePlay(source_); - is_playing_ = true; } - void buffer_stream() + void fill_stream() { - ALuint buffer; - for (int i = buffers_.size(); i <= 8; ++i) + 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) { - alGenBuffers(1, &buffer); + ALuint& buf = bufs[i]; - if (queue_.front()->stream(buffer)) + if (0 < target_diff) { - alSourceQueueBuffers(source_, 1, &buffer); - buffers_.push_back(buffer); + if (queue_buffer(buf)) --target_diff; + else + { + alDeleteBuffers(processed - i - 1, &bufs[i+1]); + break; + } } - else + else alDeleteBuffers(1, &buf); + } + + if (stream_timer_.mode() != timer::invalid && 0 < target_diff) + { + for (int i = 0; i < target_diff; ++i) { - alDeleteBuffers(1, &buffer); - break; + if (!queue_buffer(AL_NONE)) break; } } - } + 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; + if (buffer == AL_NONE) alGenBuffers(1, &buffer); - alGetSourcei(source_, AL_BUFFERS_PROCESSED, &finished); - - while (finished-- > 0) + bool looped_once = false; + + while (0 < queue_.size()) { - ALuint bufferObj; - alSourceUnqueueBuffers(source_, 1, &bufferObj); - - buffer_ptr buffer = queue_.front(); - bool streamed = buffer->stream(bufferObj); + sound_handle sound = queue_.front(); - 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"); - } - else - { - // nothing more to play, stopping... - is_playing_ = false; - std::remove(buffers_.begin(), buffers_.end(), - bufferObj); + 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; } @@ -362,6 +488,8 @@ public: is_playing_ = false; stream_timer_.invalidate(); + + sample_ = 0; } void pause() @@ -373,38 +501,31 @@ public: } - 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_; } @@ -414,7 +535,6 @@ public: ALenum type; alGetSourcei(source_, AL_SOURCE_TYPE, &type); - if (type != AL_STREAMING) { alSourcei(source_, AL_LOOPING, is_looping_); @@ -424,77 +544,35 @@ public: 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_; - std::list buffers_; + ALuint source_; - bool is_loaded_; - bool is_playing_; - bool is_looping_; + bool is_playing_; + bool is_looping_; + bool is_streaming_; - std::deque queue_; + std::deque queue_; + uint64_t sample_; + int num_buffers_; + scalar buffer_size_; - 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 +581,12 @@ 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 +606,6 @@ void sound::pause() impl_->pause(); } - void sound::toggle() { if (is_playing()) pause(); @@ -535,6 +618,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) { @@ -591,22 +679,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