1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
|
#if defined(WITH_AUDIO)
# define NCINE_INCLUDE_OPENAL
# include "../CommonHeaders.h"
#endif
#include "AudioStream.h"
#include "IAudioLoader.h"
#include "IAudioReader.h"
#include "../ServiceLocator.h"
#include <Containers/String.h>
namespace nCine
{
/*! Private constructor called only by `AudioStreamPlayer`. */
AudioStream::AudioStream()
: nextAvailableBufferIndex_(0), currentBufferId_(0), bytesPerSample_(0), numChannels_(0), isLooping_(false),
frequency_(0), numSamples_(0), duration_(0.0f), buffersIds_(NumBuffers)
{
#if defined(WITH_AUDIO)
alGetError();
alGenBuffers(NumBuffers, buffersIds_.data());
const ALenum error = alGetError();
if DEATH_UNLIKELY(error != AL_NO_ERROR) {
LOGW("alGenBuffers() failed with error 0x{:x}", error);
}
memBuffer_ = std::make_unique<char[]>(BufferSize);
#endif
}
/*! Private constructor called only by `AudioStreamPlayer`. */
AudioStream::AudioStream(StringView filename)
: AudioStream()
{
#if defined(WITH_AUDIO)
const bool hasLoaded = loadFromFile(filename);
if (!hasLoaded) {
LOGE("Audio file \"{}\" cannot be loaded", filename);
}
#endif
}
AudioStream::~AudioStream()
{
#if defined(WITH_AUDIO)
// Don't delete buffers if this is a moved out object
if (buffersIds_.size() == NumBuffers) {
alDeleteBuffers(NumBuffers, buffersIds_.data());
}
#endif
}
AudioStream::AudioStream(AudioStream&&) = default;
AudioStream& AudioStream::operator=(AudioStream&&) = default;
std::int32_t AudioStream::numStreamSamples() const
{
#if defined(WITH_AUDIO)
if (numChannels_ * bytesPerSample_ > 0) {
return BufferSize / (numChannels_ * bytesPerSample_);
}
#endif
return 0UL;
}
/*! \return A flag indicating whether the stream has been entirely decoded and played or not. */
bool AudioStream::enqueue(std::uint32_t source, bool looping)
{
#if defined(WITH_AUDIO)
if (audioReader_ == nullptr) {
return false;
}
// Set to false when the queue is empty and there is no more data to decode
bool shouldKeepPlaying = true;
ALint numProcessedBuffers;
alGetSourcei(source, AL_BUFFERS_PROCESSED, &numProcessedBuffers);
// Unqueueing
while (numProcessedBuffers > 0) {
ALuint unqueuedAlBuffer;
alSourceUnqueueBuffers(source, 1, &unqueuedAlBuffer);
nextAvailableBufferIndex_--;
buffersIds_[nextAvailableBufferIndex_] = unqueuedAlBuffer;
numProcessedBuffers--;
}
// Queueing
if (nextAvailableBufferIndex_ < NumBuffers) {
currentBufferId_ = buffersIds_[nextAvailableBufferIndex_];
std::int32_t bytes = audioReader_->read(memBuffer_.get(), BufferSize);
// EOF reached
if (bytes < BufferSize) {
if (looping) {
audioReader_->rewind();
std::int32_t moreBytes = audioReader_->read(memBuffer_.get() + bytes, BufferSize - bytes);
bytes += moreBytes;
}
}
// If it is still decoding data then enqueue
if (bytes > 0) {
// On iOS `alBufferDataStatic()` could be used instead
alBufferData(currentBufferId_, format_, memBuffer_.get(), bytes, frequency_);
alSourceQueueBuffers(source, 1, ¤tBufferId_);
nextAvailableBufferIndex_++;
}
// If there is no more data left to decode and the queue is empty
else if (nextAvailableBufferIndex_ == 0) {
shouldKeepPlaying = false;
stop(source);
}
}
ALenum state;
alGetSourcei(source, AL_SOURCE_STATE, &state);
// Handle buffer underrun case
if (state != AL_PLAYING) {
ALint numQueuedBuffers = 0;
alGetSourcei(source, AL_BUFFERS_QUEUED, &numQueuedBuffers);
if (numQueuedBuffers > 0) {
// Need to restart play
alSourcePlay(source);
}
}
return shouldKeepPlaying;
#else
return false;
#endif
}
void AudioStream::stop(std::uint32_t source)
{
#if defined(WITH_AUDIO)
// In order to unqueue all the buffers, the source must be stopped first
alSourceStop(source);
ALint numProcessedBuffers;
alGetSourcei(source, AL_BUFFERS_PROCESSED, &numProcessedBuffers);
// Unqueueing
while (numProcessedBuffers > 0) {
ALuint unqueuedAlBuffer;
alSourceUnqueueBuffers(source, 1, &unqueuedAlBuffer);
nextAvailableBufferIndex_--;
buffersIds_[nextAvailableBufferIndex_] = unqueuedAlBuffer;
numProcessedBuffers--;
}
audioReader_->rewind();
currentBufferId_ = 0;
#endif
}
void AudioStream::setLooping(bool value)
{
isLooping_ = value;
#if defined(WITH_AUDIO)
if (audioReader_ != nullptr) {
audioReader_->setLooping(value);
}
#endif
}
bool AudioStream::loadFromFile(StringView filename)
{
#if defined(WITH_AUDIO)
std::unique_ptr<IAudioLoader> audioLoader = IAudioLoader::createFromFile(filename);
if (audioLoader->hasLoaded()) {
createReader(*audioLoader);
return true;
}
#endif
return false;
}
void AudioStream::createReader(IAudioLoader& audioLoader)
{
#if defined(WITH_AUDIO)
bytesPerSample_ = audioLoader.bytesPerSample();
numChannels_ = audioLoader.numChannels();
if (numChannels_ == 1) {
format_ = (bytesPerSample_ == 2 ? AL_FORMAT_MONO16 : AL_FORMAT_MONO8);
} else if (numChannels_ == 2) {
format_ = (bytesPerSample_ == 2 ? AL_FORMAT_STEREO16 : AL_FORMAT_STEREO8);
} else {
bytesPerSample_ = 0;
numChannels_ = 0;
LOGE("Audio stream with {} channels is not supported", numChannels_);
return;
}
frequency_ = audioLoader.frequency();
numSamples_ = audioLoader.numSamples();
duration_ = (numSamples_ == UINT32_MAX ? -1.0f : float(numSamples_) / frequency_);
audioReader_ = audioLoader.createReader();
audioReader_->setLooping(isLooping_);
#endif
}
}
|