Audio: Rewrite audio module

This commit is contained in:
Jérôme Leclercq
2022-03-17 09:07:52 +01:00
parent eb4629947e
commit 6165b3a101
43 changed files with 1898 additions and 1611 deletions

View File

@@ -4,15 +4,14 @@
#include <Nazara/Audio/Music.hpp>
#include <Nazara/Audio/Algorithm.hpp>
#include <Nazara/Audio/OpenAL.hpp>
#include <Nazara/Audio/Audio.hpp>
#include <Nazara/Audio/AudioBuffer.hpp>
#include <Nazara/Audio/AudioDevice.hpp>
#include <Nazara/Audio/AudioSource.hpp>
#include <Nazara/Audio/SoundStream.hpp>
#include <Nazara/Core/CallOnExit.hpp>
#include <array>
#include <atomic>
#include <chrono>
#include <memory>
#include <thread>
#include <vector>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
@@ -25,22 +24,18 @@ namespace Nz
* \remark Module Audio needs to be initialized to use this class
*/
struct MusicImpl
Music::Music() :
Music(*Audio::Instance()->GetDefaultDevice())
{
ALenum audioFormat;
std::atomic_bool streaming = false;
std::atomic<UInt64> processedSamples;
std::vector<Int16> chunkSamples;
std::mutex bufferLock;
std::shared_ptr<SoundStream> stream;
std::thread thread;
UInt64 streamOffset;
bool loop = false;
unsigned int sampleRate;
};
Music::Music() = default;
Music::Music(Music&&) noexcept = default;
}
Music::Music(AudioDevice& device) :
SoundEmitter(device),
m_streaming(false),
m_bufferCount(2),
m_looping(false)
{
}
/*!
* \brief Destructs the object and calls Destroy
@@ -54,11 +49,10 @@ namespace Nz
/*!
* \brief Creates a music with a sound stream
* \return true if creation was succesful
* \return true if creation was successful
*
* \param soundStream Sound stream which is the source for the music
*/
bool Music::Create(std::shared_ptr<SoundStream> soundStream)
{
NazaraAssert(soundStream, "Invalid stream");
@@ -67,11 +61,10 @@ namespace Nz
AudioFormat format = soundStream->GetFormat();
m_impl = std::make_unique<MusicImpl>();
m_impl->sampleRate = soundStream->GetSampleRate();
m_impl->audioFormat = OpenAL::AudioFormat[UnderlyingCast(format)];
m_impl->chunkSamples.resize(GetChannelCount(format) * m_impl->sampleRate); // One second of samples
m_impl->stream = std::move(soundStream);
m_sampleRate = soundStream->GetSampleRate();
m_audioFormat = soundStream->GetFormat();
m_chunkSamples.resize(GetChannelCount(format) * m_sampleRate); // One second of samples
m_stream = std::move(soundStream);
SetPlayingOffset(0);
@@ -85,12 +78,7 @@ namespace Nz
*/
void Music::Destroy()
{
if (m_impl)
{
StopThread();
m_impl.reset();
}
StopThread();
}
/*!
@@ -102,9 +90,7 @@ namespace Nz
*/
void Music::EnableLooping(bool loop)
{
NazaraAssert(m_impl, "Music not created");
m_impl->loop = loop;
m_looping = loop;
}
/*!
@@ -115,9 +101,9 @@ namespace Nz
*/
UInt32 Music::GetDuration() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
return m_impl->stream->GetDuration();
return m_stream->GetDuration();
}
/*!
@@ -128,9 +114,9 @@ namespace Nz
*/
AudioFormat Music::GetFormat() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
return m_impl->stream->GetFormat();
return m_stream->GetFormat();
}
/*!
@@ -141,15 +127,14 @@ namespace Nz
*/
UInt32 Music::GetPlayingOffset() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
// Prevent music thread from enqueing new buffers while we're getting the count
std::lock_guard<std::mutex> lock(m_impl->bufferLock);
// Prevent music thread from enqueuing new buffers while we're getting the count
std::lock_guard<std::mutex> lock(m_bufferLock);
ALint samples = 0;
alGetSourcei(m_source, AL_SAMPLE_OFFSET, &samples);
UInt32 sampleOffset = m_source->GetSampleOffset();
return static_cast<UInt32>((1000ULL * (samples + (m_impl->processedSamples / GetChannelCount(m_impl->stream->GetFormat())))) / m_impl->sampleRate);
return static_cast<UInt32>((1000ULL * (sampleOffset + (m_processedSamples / GetChannelCount(m_stream->GetFormat())))) / m_sampleRate);
}
/*!
@@ -160,9 +145,9 @@ namespace Nz
*/
UInt64 Music::GetSampleCount() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
return m_impl->stream->GetSampleCount();
return m_stream->GetSampleCount();
}
/*!
@@ -173,9 +158,9 @@ namespace Nz
*/
UInt32 Music::GetSampleRate() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
return m_impl->sampleRate;
return m_sampleRate;
}
/*!
@@ -186,12 +171,12 @@ namespace Nz
*/
SoundStatus Music::GetStatus() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
SoundStatus status = GetInternalStatus();
SoundStatus status = m_source->GetStatus();
// To compensate any delays (or the timelaps between Play() and the thread startup)
if (m_impl->streaming && status == SoundStatus::Stopped)
if (m_streaming && status == SoundStatus::Stopped)
status = SoundStatus::Playing;
return status;
@@ -205,9 +190,7 @@ namespace Nz
*/
bool Music::IsLooping() const
{
NazaraAssert(m_impl, "Music not created");
return m_impl->loop;
return m_looping;
}
/*!
@@ -267,9 +250,7 @@ namespace Nz
*/
void Music::Pause()
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcePause(m_source);
m_source->Pause();
}
/*!
@@ -284,10 +265,10 @@ namespace Nz
*/
void Music::Play()
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
// Maybe we are already playing
if (m_impl->streaming)
if (m_streaming)
{
switch (GetStatus())
{
@@ -296,7 +277,7 @@ namespace Nz
break;
case SoundStatus::Paused:
alSourcePlay(m_source);
m_source->Play();
break;
default:
@@ -309,12 +290,12 @@ namespace Nz
std::condition_variable cv;
// Starting streaming thread
m_impl->streaming = true;
m_streaming = true;
std::exception_ptr exceptionPtr;
std::unique_lock<std::mutex> lock(mutex);
m_impl->thread = std::thread(&Music::MusicThread, this, std::ref(cv), std::ref(mutex), std::ref(exceptionPtr));
m_thread = std::thread(&Music::MusicThread, this, std::ref(cv), std::ref(mutex), std::ref(exceptionPtr));
// Wait until thread signal it has properly started (or an error occurred)
cv.wait(lock);
@@ -335,17 +316,17 @@ namespace Nz
*/
void Music::SetPlayingOffset(UInt32 offset)
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
bool isPlaying = m_impl->streaming;
bool isPlaying = m_streaming;
if (isPlaying)
Stop();
UInt64 sampleOffset = UInt64(offset) * m_impl->sampleRate * GetChannelCount(m_impl->stream->GetFormat()) / 1000ULL;
UInt64 sampleOffset = UInt64(offset) * m_sampleRate * GetChannelCount(m_stream->GetFormat()) / 1000ULL;
m_impl->processedSamples = sampleOffset;
m_impl->streamOffset = sampleOffset;
m_processedSamples = sampleOffset;
m_streamOffset = sampleOffset;
if (isPlaying)
Play();
@@ -358,31 +339,27 @@ namespace Nz
*/
void Music::Stop()
{
NazaraAssert(m_impl, "Music not created");
StopThread();
SetPlayingOffset(0);
}
Music& Music::operator=(Music&&) noexcept = default;
bool Music::FillAndQueueBuffer(unsigned int buffer)
bool Music::FillAndQueueBuffer(std::shared_ptr<AudioBuffer> buffer)
{
std::size_t sampleCount = m_impl->chunkSamples.size();
std::size_t sampleCount = m_chunkSamples.size();
std::size_t sampleRead = 0;
{
std::lock_guard<std::mutex> lock(m_impl->stream->GetMutex());
std::lock_guard<std::mutex> lock(m_stream->GetMutex());
m_impl->stream->Seek(m_impl->streamOffset);
m_stream->Seek(m_streamOffset);
// Fill the buffer by reading from the stream
for (;;)
{
sampleRead += m_impl->stream->Read(&m_impl->chunkSamples[sampleRead], sampleCount - sampleRead);
if (sampleRead < sampleCount && m_impl->loop)
sampleRead += m_stream->Read(&m_chunkSamples[sampleRead], sampleCount - sampleRead);
if (sampleRead < sampleCount && m_looping)
{
// In case we read less than expected, assume we reached the end of the stream and seek back to the beginning
m_impl->stream->Seek(0);
m_stream->Seek(0);
continue;
}
@@ -390,14 +367,14 @@ namespace Nz
break;
}
m_impl->streamOffset = m_impl->stream->Tell();
m_streamOffset = m_stream->Tell();
}
// Update the buffer (send it to OpenAL) and queue it if we got any data
// Update the buffer on the AudioDevice and queue it if we got any data
if (sampleRead > 0)
{
alBufferData(buffer, m_impl->audioFormat, &m_impl->chunkSamples[0], static_cast<ALsizei>(sampleRead*sizeof(Int16)), static_cast<ALsizei>(m_impl->sampleRate));
alSourceQueueBuffers(m_source, 1, &buffer);
buffer->Reset(m_audioFormat, sampleRead, m_sampleRate, &m_chunkSamples[0]);
m_source->QueueBuffer(buffer);
}
return sampleRead != sampleCount; // End of stream (Does not happen when looping)
@@ -406,20 +383,18 @@ namespace Nz
void Music::MusicThread(std::condition_variable& cv, std::mutex& m, std::exception_ptr& err)
{
// Allocation of streaming buffers
std::array<ALuint, NAZARA_AUDIO_STREAMED_BUFFER_COUNT> buffers;
alGenBuffers(NAZARA_AUDIO_STREAMED_BUFFER_COUNT, buffers.data());
CallOnExit freebuffers([&]
CallOnExit unqueueBuffers([&]
{
alDeleteBuffers(NAZARA_AUDIO_STREAMED_BUFFER_COUNT, buffers.data());
m_source->UnqueueAllBuffers();
});
try
{
for (ALuint buffer : buffers)
for (std::size_t i = 0; i < m_bufferCount; ++i)
{
if (FillAndQueueBuffer(buffer))
std::shared_ptr<AudioBuffer> buffer = m_source->GetAudioDevice()->CreateBuffer();
if (FillAndQueueBuffer(std::move(buffer)))
break; // We have reached the end of the stream, there is no use to add new buffers
}
}
@@ -432,23 +407,12 @@ namespace Nz
return;
}
CallOnExit unqueueBuffers([&]
{
// We delete buffers from the stream
ALint queuedBufferCount;
alGetSourcei(m_source, AL_BUFFERS_QUEUED, &queuedBufferCount);
m_source->Play();
ALuint buffer;
for (ALint i = 0; i < queuedBufferCount; ++i)
alSourceUnqueueBuffers(m_source, 1, &buffer);
});
alSourcePlay(m_source);
CallOnExit stopSource([&]
{
// Stop playing of the sound (in the case where it has not been already done)
alSourceStop(m_source);
m_source->Stop();
});
// Signal we're good
@@ -458,35 +422,25 @@ namespace Nz
} // m & cv no longer exists from here
// Reading loop (Filling new buffers as playing)
while (m_impl->streaming)
while (m_streaming)
{
// The reading has stopped, we have reached the end of the stream
SoundStatus status = GetInternalStatus();
SoundStatus status = m_source->GetStatus();
if (status == SoundStatus::Stopped)
{
m_impl->streaming = false;
// The reading has stopped, we have reached the end of the stream
m_streaming = false;
break;
}
{
std::lock_guard<std::mutex> lock(m_impl->bufferLock);
std::lock_guard<std::mutex> lock(m_bufferLock);
// We treat read buffers
ALint processedCount = 0;
alGetSourcei(m_source, AL_BUFFERS_PROCESSED, &processedCount);
while (processedCount--)
while (std::shared_ptr<AudioBuffer> buffer = m_source->TryUnqueueProcessedBuffer())
{
ALuint buffer;
alSourceUnqueueBuffers(m_source, 1, &buffer);
m_processedSamples += buffer->GetSampleCount();
ALint bits, size;
alGetBufferi(buffer, AL_BITS, &bits);
alGetBufferi(buffer, AL_SIZE, &size);
if (bits != 0)
m_impl->processedSamples += (8 * size) / bits;
if (FillAndQueueBuffer(buffer))
if (FillAndQueueBuffer(std::move(buffer)))
break;
}
}
@@ -498,10 +452,10 @@ namespace Nz
void Music::StopThread()
{
if (m_impl->streaming)
m_impl->streaming = false;
if (m_streaming)
m_streaming = false;
if (m_impl->thread.joinable())
m_impl->thread.join();
if (m_thread.joinable())
m_thread.join();
}
}