#include <string>
#include <boost/algorithm/string.hpp>
+#include <boost/cstdint.hpp>
#include <AL/al.h>
#include <AL/alc.h>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
+#include "hash.hh"
#include "log.hh"
#include "manager.hh"
#include "sound.hh"
+#include "resource.hh"
#include "timer.hh"
-#define BUF_SIZE (64 * 1024)
-//#define BUF_SIZE (5*2048)
+
+#ifndef BUF_SIZE
+#define BUF_SIZE (4096)
+#endif
+
+#define NUM_BUFFERS (16)
+
namespace moof {
-class impl
+class sound_backend
+{
+public:
+
+ 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)
+ {
+ ++retain_count;
+ }
+
+ sound_backend& operator=(const sound_backend& backend)
+ {
+ ++retain_count;
+ return *this;
+ }
+
+ ~sound_backend()
+ {
+ if (--retain_count == 0)
+ {
+ alcMakeContextCurrent(0);
+ alcDestroyContext(al_context);
+ alcCloseDevice(al_device);
+ log_info("unloaded sound device ALSA");
+ }
+ }
+
+
+ static int retain_count;
+ static ALCdevice* al_device;
+ static ALCcontext* al_context;
+};
+
+int sound_backend::retain_count = 0;
+ALCdevice* sound_backend::al_device;
+ALCcontext* sound_backend::al_context;
+
+
+class sound_resource;
+typedef resource_handle<sound_resource> sound_handle;
+
+
+class sound_resource_loader
{
public:
- impl()
+ sound_resource_loader()
{
- resource::register_type<sound_stream>("ogg");
+ resource::register_type<sound_resource>("ogg");
}
- ~impl()
+ ~sound_resource_loader()
{
resource::unregister_type("ogg");
}
};
-static impl impl;
+static sound_resource_loader loader;
-class sound::impl
+
+
+// SOUND BUFFER
+
+class buffer
{
public:
- static ALenum get_audio_format(const vorbis_info* audioInfo)
+ typedef hash<ALuint,int,hash_function> retcount_lookup;
+
+
+ buffer() :
+ buffer_((ALuint)-1) {}
+
+ buffer(const void* data,
+ ALsizei size,
+ ALenum format,
+ ALsizei freq)
{
- if (audioInfo->channels == 1) return AL_FORMAT_MONO16;
- else return AL_FORMAT_STEREO16;
+ alGenBuffers(1, &buffer_);
+ alBufferData(buffer_, format, data, size, freq);
+
+ retain_counts_[buffer_] = 1;
+ log_warning("ctor buffer:", buffer_);
}
-
- class buffer;
- typedef boost::shared_ptr<buffer> buffer_ptr;
-
- class buffer : public manager<buffer>
+ buffer(const buffer& buf)
{
- public:
+ buffer_ = buf.buffer_;
+ retain();
+ }
- buffer() :
- buffer_(-1)
+ buffer& operator = (const buffer& buf)
+ {
+ buffer_ = buf.buffer_;
+ retain();
+ return *this;
+ }
+
+ ~buffer()
+ {
+ release();
+ }
+
+
+ void queue(ALuint source) const
+ {
+ if (*this)
{
- mOggStream.datasource = 0;
+ alSourceQueueBuffers(source, 1, &buffer_);
+ retain();
+ log_warning("queued buffer:", buffer_);
}
+ }
+
+ static buffer unqueue(ALuint source)
+ {
+ ALuint buf = (ALuint)-1;
+ alSourceUnqueueBuffers(source, 1, &buf);
+ log_warning("unqueued buffer:", buf);
+ return buffer(buf);
+ }
+
+ void set(ALuint source) const
+ {
+ log_warning("set buffer:", buffer_);
+ if (*this) alSourcei(source, AL_BUFFER, buffer_);
+ }
+
+ operator bool () const
+ {
+ return buffer_ != (ALuint)-1;
+ }
+
+
+private:
- ~buffer()
+ 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)
{
- if (mOggStream.datasource)
- {
- ov_clear(&mOggStream);
- }
- if (int(buffer_) != -1) alDeleteBuffers(1, &buffer_);
+ alDeleteBuffers(1, &buffer_);
+ retain_counts_.erase(it);
+ log_warning("kill buffer:", buffer_);
}
+ }
- void init(const std::string& path)
- {
- log_info("initializing audio buffer...");
- if (mOggStream.datasource)
- {
- ov_clear(&mOggStream);
- mOggStream.datasource = 0;
- }
+ ALuint buffer_;
+ sound_backend backend_;
- if (ov_fopen((char*)path.c_str(), &mOggStream) < 0)
- {
- throw std::runtime_error("problem reading audio: " + path);
- }
+ static retcount_lookup retain_counts_;
+};
- vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
- mFormat = get_audio_format(vorbisInfo);
- mFreq = vorbisInfo->rate;
- }
+buffer::retcount_lookup buffer::retain_counts_;
- void load_all(ALuint source)
- {
- if (!mOggStream.datasource) init(name());
- if (!mOggStream.datasource) return;
- char data[BUF_SIZE];
- int size = 0;
+// SOUND RESOURCE
- for (;;)
- {
- int section;
- int result = ov_read(&mOggStream, data + size,
- BUF_SIZE - size, 0, 2, 1, §ion);
+class sound_resource
+{
+public:
- 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;
- }
+ sound_resource(const std::string& path)
+ {
+ log_info("audio path is", path);
+ if (ov_fopen((char*)path.c_str(), &file_) < 0)
+ {
+ throw std::runtime_error("problem reading audio: " + path);
+ }
+ }
+
+ ~sound_resource()
+ {
+ ov_clear(&file_);
+ }
- alGenBuffers(1, &buffer_);
- alBufferData(buffer_, mFormat, data, size, mFreq);
- alSourcei(source, AL_BUFFER, buffer_);
+ bool read(buffer& buf)
+ {
+ if (buffer_)
+ {
+ buf = buffer_;
+ return true;
+ }
- // don't need to keep this loaded
- ov_clear(&mOggStream);
- mOggStream.datasource = 0;
+ if (ov_pcm_seek_lap(&file_, 0) != 0)
+ {
+ log_warning("vorbis seek error");
+ return false;
}
- bool stream(ALuint buffer)
+ char data[64*BUF_SIZE];
+ size_t size = 0;
+
+ while (size < sizeof(data))
{
- char data[BUF_SIZE];
- int size = 0;
+ int section;
+ int result = ov_read(&file_,
+ data + size, sizeof(data) - size,
+ 0, 2, 1, §ion);
- while (size < BUF_SIZE)
+ if (result > 0)
{
- 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;
- }
+ size += result;
+ continue;
}
+ else if (result == 0 && size > 0)
+ {
+ log_info("loaded", size, "bytes from vorbis");
+ vorbis_info* info = ov_info(&file_, section);
+ buffer_ = buffer(data, size,
+ get_audio_format(info), info->rate);
+ buf = buffer_;
+ log_info("this section is", section);
+ log_info("audio format is", get_audio_format(info));
+ log_info("audio freq is", info->rate);
+ return true;
+ }
+ else
+ {
+ log_warning("vorbis playback error");
+ break;
+ }
+ }
- if (size == 0) return false;
+ if (size >= sizeof(data)) log_warning("sample is too big to play");
+ return false;
+ }
- alBufferData(buffer, mFormat, data, size, mFreq);
- return true;
+ bool read(buffer& buf, uint64_t& sample)
+ {
+ if (ov_pcm_seek_lap(&file_, sample) != 0)
+ {
+ log_warning("vorbis seek error");
+ return false;
}
- void rewind()
+ char data[BUF_SIZE];
+ int section;
+ int result = ov_read(&file_, data, sizeof(data), 0, 2, 1, §ion);
+
+ if (result > 0)
{
- if (!mOggStream.datasource) init(name());
- else ov_raw_seek(&mOggStream, 0);
+ log_info("loaded", result, "bytes from vorbis");
+ vorbis_info* info = ov_info(&file_, section);
+ buf = buffer(data, result, get_audio_format(info), info->rate);
+ sample = ov_pcm_tell(&file_);
+ log_info("this section is", section);
+ log_info("next sample is", sample);
+ log_info("audio format is", get_audio_format(info));
+ log_info("audio freq is", info->rate);
+ return true;
}
+ if (result < 0) log_warning("vorbis playback error");
+ return false;
+ }
- private:
- OggVorbis_File mOggStream;
- ALenum mFormat;
- ALsizei mFreq;
- ALuint buffer_;
- };
+ static ALenum get_audio_format(const vorbis_info* info)
+ {
+ if (info->channels == 1) return AL_FORMAT_MONO16;
+ else return AL_FORMAT_STEREO16;
+ }
+private:
+
+ OggVorbis_File file_;
+ buffer buffer_;
+};
+
+
+
+class sound::impl
+{
+public:
+
impl()
{
init();
void init()
{
- retain_backend();
-
- is_loaded_ = false;
is_playing_ = false;
is_looping_ = false;
+ sample_ = 0;
+
alGenSources(1, &source_);
+ log_error("alGenSources:", alGetError());
ALfloat zero[] = {0.0f, 0.0f, 0.0f};
alSourcef(source_, AL_PITCH, 1.0f);
alSourcef(source_, AL_GAIN, 1.0f);
alSourcefv(source_, AL_POSITION, zero);
alSourcefv(source_, AL_VELOCITY, zero);
-
- alSourcei(source_, AL_LOOPING, is_looping_);
+ log_error("init:", alGetError());
}
~impl()
{
stop();
-
alDeleteSources(1, &source_);
-
- while (!buffers_.empty())
- {
- alDeleteBuffers(1, &buffers_.back());
- buffers_.pop_back();
- }
-
- release_backend();
}
{
if (queue_.empty()) return;
- if (!is_loaded_) queue_.front()->load_all(source_);
+ sound_handle handle = queue_.front();
+ buffer buf;
- alSourcePlay(source_);
- is_loaded_ = true;
+ if (handle->read(buf))
+ {
+ log_info("playing source...");
+ buf.set(source_);
+ alSourcei(source_, AL_LOOPING, is_looping_);
+ alSourcePlay(source_);
+ }
}
-
- void play_stream()
+ void stream()
{
if (queue_.empty()) return;
if (!is_playing_)
{
alSourcei(source_, AL_LOOPING, false);
- buffer_stream();
+ log_error("set not looping:", alGetError());
+
+ sound_handle handle = queue_.front();
+
+ for (int i = 0; i < NUM_BUFFERS; ++i)
+ {
+ buffer buf;
+ if (handle->read(buf, sample_))
+ {
+ buf.queue(source_);
+ }
+ else
+ {
+ log_error("failed to start stream");
+ break;
+ }
+
+ ALint queued = 0;
+ alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
+ log_info("buffers queued:", queued);
+ }
}
if (!stream_timer_.is_valid())
{
stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2),
- 1.0, timer::repeat);
+ 0.01, timer::repeat);
}
+ log_info("streaming source...");
alSourcePlay(source_);
+ log_error("playing:", alGetError());
is_playing_ = true;
}
- void buffer_stream()
- {
- ALuint buffer;
- for (int i = buffers_.size(); i <= 8; ++i)
- {
- alGenBuffers(1, &buffer);
-
- if (queue_.front()->stream(buffer))
- {
- alSourceQueueBuffers(source_, 1, &buffer);
- buffers_.push_back(buffer);
- }
- else
- {
- alDeleteBuffers(1, &buffer);
- break;
- }
- }
- }
-
void update()
{
while (finished-- > 0)
{
- ALuint bufferObj;
- alSourceUnqueueBuffers(source_, 1, &bufferObj);
-
- buffer_ptr buffer = queue_.front();
- bool streamed = buffer->stream(bufferObj);
+ 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)
{
- alSourceQueueBuffers(source_, 1, &bufferObj);
+ buf.queue(source_);
}
else
{
// the buffer couldn't be streamed, so get rid of it
queue_.pop_front();
+ sample_ = 0;
if (!queue_.empty())
{
// begin the next buffer in the queue
- queue_.front()->rewind();
- queue_.front()->stream(bufferObj);
- alSourceQueueBuffers(source_, 1, &bufferObj);
+ handle->read(buf, sample_);
+ buf.queue(source_);
log_info("loading new buffer");
-
- // queue up any unused buffers
- buffer_stream();
}
else if (is_looping_)
{
// 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(handle);
+ handle->read(buf, sample_);
+ buf.queue(source_);
}
else
{
// nothing more to play, stopping...
- is_playing_ = false;
- std::remove(buffers_.begin(), buffers_.end(),
- bufferObj);
+ stop();
+ queue_.push_back(handle);
}
}
}
is_playing_ = false;
stream_timer_.invalidate();
+
+ // TODO: clear buffers if streaming
}
void pause()
stream_timer_.invalidate();
}
+ void rewind()
+ {
+ alSourceRewind(source_);
+ sample_ = 0;
+ }
+
void sample(const std::string& path)
{
alSourcei(source_, AL_BUFFER, AL_NONE);
queue_.clear();
- is_loaded_ = false;
enqueue(path);
-
- while (!buffers_.empty())
- {
- alDeleteBuffers(1, &buffers_.back());
- buffers_.pop_back();
- }
}
void enqueue(const std::string& path)
{
- buffer_ptr buffer = buffer::instance(path);
- queue_.push_back(buffer);
+ sound_handle handle = resource::load(path);
+ queue_.push_back(handle);
}
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_;
}
}
- 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_);
- }
- }
+ ALuint source_;
+ bool is_playing_;
+ bool is_looping_;
- ALuint source_;
- std::list<ALuint> buffers_;
+ std::deque<sound_handle> queue_;
+ uint64_t sample_;
- bool is_loaded_;
- bool is_playing_;
- bool is_looping_;
+ timer stream_timer_;
- std::deque<buffer_ptr> queue_;
-
- 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_->sample(path);
}
+void sound::enqueue(const std::string& path)
+{
+ // pass through
+ impl_->enqueue(path);
+}
+
void sound::play()
{
impl_->play();
}
+void sound::stream()
+{
+ // pass through
+ impl_->stream();
+}
+
void sound::stop()
{
// pass through
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
}
-//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
-void sound_stream::enqueue(const std::string& path)
-{
- // pass through
- impl_->enqueue(path);
-}
-
-
-void sound_stream::play()
-{
- // pass through
- impl_->play_stream();
-}
-
-
} // namespace moof