381 lines
8.3 KiB
C++
381 lines
8.3 KiB
C++
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include <algorithm>
|
|
|
|
#include <AL/al.h>
|
|
|
|
#include "ThreadPool.h"
|
|
#include "SoundEngine.h"
|
|
#include "SoundSource.h"
|
|
#include "SoundResource.h"
|
|
#include "SoundResourceWAV.h"
|
|
|
|
ALCdevice *SoundEngine::m_device = NULL;
|
|
ALCcontext *SoundEngine::m_context = NULL;
|
|
|
|
ThreadPool *SoundEngine::m_threadPool = NULL;
|
|
|
|
ALuint SoundEngine::m_bufferPool[SOUNDENGINE_BUFFER_POOL_SIZE];
|
|
size_t SoundEngine::m_bufferPoolSize = 0;
|
|
|
|
Mutex *SoundEngine::m_bufferPoolMutex = NULL;
|
|
Mutex *SoundEngine::m_resourceMutex = NULL;
|
|
|
|
std::vector<RefCounted<SoundResource *>> SoundEngine::m_soundResources;
|
|
std::vector<SoundSource *> SoundEngine::m_soundSources;
|
|
|
|
std::vector<SoundSource *> SoundEngine::m_sourcesPendingDeletion;
|
|
|
|
bool SoundEngine::Init()
|
|
{
|
|
m_device = alcOpenDevice(NULL);
|
|
|
|
if (!m_device)
|
|
{
|
|
printf("Failed to open OpenAL device\n");
|
|
return false;
|
|
}
|
|
|
|
m_context = alcCreateContext(m_device, NULL);
|
|
|
|
if (!m_context)
|
|
{
|
|
printf("Failed to create OpenAL context\n");
|
|
alcCloseDevice(m_device);
|
|
return false;
|
|
}
|
|
|
|
alcMakeContextCurrent(m_context);
|
|
|
|
printf("OpenAL '%s' running on '%s'\n", alGetString(AL_VERSION), alGetString(AL_RENDERER));
|
|
|
|
#if 1
|
|
ALCenum error = alcGetError(m_device);
|
|
|
|
if (error != ALC_NO_ERROR)
|
|
printf("alcGetError=%s\n", alcGetString(m_device, error));
|
|
#endif
|
|
|
|
m_threadPool = new ThreadPool();
|
|
|
|
alGenBuffers(SOUNDENGINE_BUFFER_POOL_SIZE, m_bufferPool);
|
|
m_bufferPoolSize = SOUNDENGINE_BUFFER_POOL_SIZE;
|
|
|
|
m_bufferPoolMutex = new Mutex();
|
|
m_resourceMutex = new Mutex();
|
|
|
|
return true;
|
|
}
|
|
|
|
void SoundEngine::Release()
|
|
{
|
|
#if 1
|
|
ALCenum error = alcGetError(m_device);
|
|
|
|
if (error != ALC_NO_ERROR)
|
|
printf("alcGetError=%s\n", alcGetString(m_device, error));
|
|
#endif
|
|
|
|
// I think all currently queued tasks on the thread pool are canceled if we free it.
|
|
delete m_threadPool;
|
|
|
|
#if 0
|
|
SoundSource *source;
|
|
|
|
while(!m_soundSources.empty())
|
|
{
|
|
source = m_soundSources.back();
|
|
m_soundSources.pop_back();
|
|
|
|
printf("Releasing sound source in SoundEngine::Release\n");
|
|
|
|
delete source;
|
|
}
|
|
|
|
while(!m_sourcesPendingDeletion.empty())
|
|
{
|
|
source = m_sourcesPendingDeletion.back();
|
|
m_sourcesPendingDeletion.pop_back();
|
|
|
|
printf("Releasing sound source in SoundEngine::Release\n");
|
|
|
|
delete source;
|
|
}
|
|
|
|
SoundResource *resource;
|
|
|
|
while(!m_soundResources.empty())
|
|
{
|
|
resource = m_soundResources.back().GetValue();
|
|
m_soundResources.pop_back();
|
|
|
|
printf("Releasing resource '%s' in SoundEngine::Release\n", resource->GetName().c_str());
|
|
|
|
delete resource;
|
|
}
|
|
#endif
|
|
|
|
//assert(m_bufferPoolSize == SOUNDENGINE_BUFFER_POOL_SIZE);
|
|
printf("%i buffers missing from the pool at SoundEngine::Release\n", SOUNDENGINE_BUFFER_POOL_SIZE - (int)m_bufferPoolSize);
|
|
|
|
alDeleteBuffers((ALsizei)m_bufferPoolSize, m_bufferPool);
|
|
|
|
alcMakeContextCurrent(NULL);
|
|
alcDestroyContext(m_context);
|
|
alcCloseDevice(m_device);
|
|
|
|
delete m_bufferPoolMutex;
|
|
delete m_resourceMutex;
|
|
}
|
|
|
|
SoundResource *SoundEngine::GetSoundResource(const std::string &name)
|
|
{
|
|
SoundResource *resource = NULL;
|
|
|
|
m_resourceMutex->Lock();
|
|
|
|
for(auto i = m_soundResources.begin(); i != m_soundResources.end(); i++)
|
|
{
|
|
if ((*i).GetValue()->GetName() == name)
|
|
{
|
|
(*i).IncRefCount();
|
|
resource = (*i).GetValue();
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_resourceMutex->Unlock();
|
|
|
|
if (!resource)
|
|
{
|
|
resource = SoundResourceWAV::Load(name);
|
|
|
|
if (resource)
|
|
{
|
|
m_resourceMutex->Lock();
|
|
m_soundResources.push_back(RefCounted<SoundResource *>(resource));
|
|
m_resourceMutex->Unlock();
|
|
}
|
|
}
|
|
|
|
return resource;
|
|
}
|
|
|
|
void SoundEngine::ReleaseSoundResource(SoundResource *resource)
|
|
{
|
|
m_resourceMutex->Lock();
|
|
|
|
for(auto i = m_soundResources.begin(); i != m_soundResources.end(); i++)
|
|
{
|
|
if ((*i).GetValue() == resource)
|
|
{
|
|
int refCount = (*i).DecRefCount();
|
|
|
|
if (refCount == 0)
|
|
{
|
|
//printf("'%s' reached refcount 0, deleting\n", (*i).GetValue()->GetName().c_str());
|
|
|
|
delete (*i).GetValue();
|
|
m_soundResources.erase(i);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_resourceMutex->Unlock();
|
|
}
|
|
|
|
void SoundEngine::SetDopplerFactor(float dopplerFactor)
|
|
{
|
|
alDopplerFactor(dopplerFactor);
|
|
}
|
|
|
|
void SoundEngine::SetSpeedOfSound(float speed)
|
|
{
|
|
alSpeedOfSound(speed);
|
|
}
|
|
|
|
SoundSource *SoundEngine::CreateSoundSource()
|
|
{
|
|
SoundSource *source = new SoundSource();
|
|
m_soundSources.push_back(source);
|
|
|
|
return source;
|
|
}
|
|
|
|
void SoundEngine::DestroySoundSource(SoundSource *source)
|
|
{
|
|
for(auto i = m_soundSources.begin(); i != m_soundSources.end(); i++)
|
|
{
|
|
if ((*i) == source)
|
|
{
|
|
m_soundSources.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we delete it immediately then the resource/buffer loading task will try to access the freed memory.
|
|
if (source->m_isLoadingBuffer || source->m_isLoadingResource)
|
|
{
|
|
alSourceStop(source->m_source);
|
|
m_sourcesPendingDeletion.push_back(source);
|
|
}
|
|
else
|
|
{
|
|
delete source;
|
|
}
|
|
}
|
|
|
|
ALuint SoundEngine::GetBuffer()
|
|
{
|
|
ALuint buffer = AL_NONE;
|
|
|
|
m_bufferPoolMutex->Lock();
|
|
|
|
if (m_bufferPoolSize > 0)
|
|
{
|
|
m_bufferPoolSize--;
|
|
buffer = m_bufferPool[m_bufferPoolSize];
|
|
}
|
|
else
|
|
{
|
|
printf("The sound buffer pool is empty!\n");
|
|
}
|
|
|
|
m_bufferPoolMutex->Unlock();
|
|
|
|
return buffer;
|
|
}
|
|
|
|
void SoundEngine::ReleaseBuffer(ALuint buffer)
|
|
{
|
|
// Free the data stored in the buffer.
|
|
alBufferData(buffer, AL_FORMAT_MONO8, NULL, 0, 0);
|
|
|
|
m_bufferPoolMutex->Lock();
|
|
assert(m_bufferPoolSize < SOUNDENGINE_BUFFER_POOL_SIZE);
|
|
m_bufferPool[m_bufferPoolSize++] = buffer;
|
|
m_bufferPoolMutex->Unlock();
|
|
}
|
|
|
|
void SoundEngine::Update()
|
|
{
|
|
SoundSource *source;
|
|
SoundResource *resource;
|
|
ALint state;
|
|
ALuint buffer = AL_NONE;
|
|
size_t sampleOffset, sampleCount;
|
|
bool hasReachedEnd, shouldStop;
|
|
int processedCount = 0;
|
|
int queuedCount = 0;
|
|
int j;
|
|
|
|
for(size_t i = 0; i < m_sourcesPendingDeletion.size(); i++)
|
|
{
|
|
source = m_sourcesPendingDeletion[i];
|
|
|
|
if (!source->m_isLoadingBuffer && !source->m_isLoadingResource)
|
|
{
|
|
m_sourcesPendingDeletion.erase(m_sourcesPendingDeletion.begin() + i);
|
|
i--;
|
|
|
|
delete source;
|
|
|
|
// printf("Deleted source in m_sourcesPendingDeletion\n");
|
|
}
|
|
}
|
|
|
|
for(auto i = m_soundSources.begin(); i != m_soundSources.end(); i++)
|
|
{
|
|
source = (*i);
|
|
resource = source->m_resource;
|
|
|
|
if (!resource)
|
|
continue;
|
|
|
|
if (!resource->IsStreaming())
|
|
{
|
|
alGetSourcei(source->m_source, AL_SOURCE_STATE, &state);
|
|
|
|
if (!source->m_isLoadingResource && source->m_isPlaying && state != AL_PLAYING)
|
|
{
|
|
source->m_isPlaying = false;
|
|
|
|
//printf("Reached end of non-streaming source\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
alGetSourcei(source->m_source, AL_BUFFERS_PROCESSED, &processedCount);
|
|
|
|
for(j = 0; j < processedCount; j++)
|
|
{
|
|
alSourceUnqueueBuffers(source->m_source, 1, &buffer);
|
|
ReleaseBuffer(buffer);
|
|
}
|
|
|
|
alGetSourcei(source->m_source, AL_BUFFERS_QUEUED, &queuedCount);
|
|
hasReachedEnd = (source->m_nextSample == resource->GetTotalSampleCount());
|
|
shouldStop = (hasReachedEnd && !source->IsLooping());
|
|
|
|
if (shouldStop && queuedCount == 0)
|
|
{
|
|
source->m_isPlaying = false;
|
|
// Immediately start loading samples from the beginning again.
|
|
shouldStop = false;
|
|
}
|
|
|
|
if (!source->m_isLoadingBuffer && !shouldStop && queuedCount < SOUNDENGINE_STREAMING_BUFFER_COUNT)
|
|
{
|
|
if (hasReachedEnd)
|
|
source->m_nextSample = 0;
|
|
|
|
sampleOffset = source->m_nextSample;
|
|
sampleCount = resource->GetSampleCountForTime(SOUNDENGINE_STREAMING_BUFFER_SIZE);
|
|
sampleCount = std::min(sampleCount, resource->GetTotalSampleCount() - sampleOffset);
|
|
|
|
assert(sampleCount > 0);
|
|
|
|
std::function<void()> task = [source, resource, sampleOffset, sampleCount]() {
|
|
if (source->m_cancelBufferLoading)
|
|
{
|
|
source->m_cancelBufferLoading = false;
|
|
source->m_isLoadingBuffer = false;
|
|
|
|
printf("Buffer loading was canceled\n");
|
|
|
|
return;
|
|
}
|
|
|
|
ALuint buffer = GetBuffer();
|
|
resource->FillBuffer(buffer, sampleOffset, sampleCount);
|
|
alSourceQueueBuffers(source->m_source, 1, &buffer);
|
|
|
|
ALint state;
|
|
alGetSourcei(source->m_source, AL_SOURCE_STATE, &state);
|
|
|
|
// If we were playing but ran out of buffers, restart playback.
|
|
if (source->m_isPlaying && state != AL_PLAYING)
|
|
source->Play();
|
|
|
|
source->m_nextSample = sampleOffset + sampleCount;
|
|
source->m_cancelBufferLoading = false;
|
|
source->m_isLoadingBuffer = false;
|
|
};
|
|
|
|
source->m_isLoadingBuffer = true;
|
|
m_threadPool->EnqueueTask(task);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 1
|
|
ALenum error = alGetError();
|
|
|
|
if (error != AL_NO_ERROR)
|
|
printf("alGetError=%s (0x%x)\n", alGetString(error), error);
|
|
#endif
|
|
}
|