Audio/Music: Rework Play() (ensure music has started before return and handle exceptions in thread)

This commit is contained in:
Jérôme Leclercq 2021-05-25 12:12:15 +02:00
parent c880a431a2
commit 001c9a6a61
2 changed files with 68 additions and 24 deletions

View File

@ -11,6 +11,9 @@
#include <Nazara/Audio/Enums.hpp> #include <Nazara/Audio/Enums.hpp>
#include <Nazara/Audio/SoundEmitter.hpp> #include <Nazara/Audio/SoundEmitter.hpp>
#include <Nazara/Audio/SoundStream.hpp> #include <Nazara/Audio/SoundStream.hpp>
#include <condition_variable>
#include <exception>
#include <mutex>
namespace Nz namespace Nz
{ {
@ -56,7 +59,7 @@ namespace Nz
std::unique_ptr<MusicImpl> m_impl; std::unique_ptr<MusicImpl> m_impl;
bool FillAndQueueBuffer(unsigned int buffer); bool FillAndQueueBuffer(unsigned int buffer);
void MusicThread(); void MusicThread(std::condition_variable& cv, std::mutex& m, std::exception_ptr& err);
void StopThread(); void StopThread();
}; };
} }

View File

@ -6,10 +6,11 @@
#include <Nazara/Audio/Algorithm.hpp> #include <Nazara/Audio/Algorithm.hpp>
#include <Nazara/Audio/OpenAL.hpp> #include <Nazara/Audio/OpenAL.hpp>
#include <Nazara/Audio/SoundStream.hpp> #include <Nazara/Audio/SoundStream.hpp>
#include <Nazara/Core/CallOnExit.hpp>
#include <array>
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <mutex>
#include <thread> #include <thread>
#include <vector> #include <vector>
#include <Nazara/Audio/Debug.hpp> #include <Nazara/Audio/Debug.hpp>
@ -27,6 +28,7 @@ namespace Nz
struct MusicImpl struct MusicImpl
{ {
ALenum audioFormat; ALenum audioFormat;
std::atomic_bool streaming = false;
std::atomic<UInt64> processedSamples; std::atomic<UInt64> processedSamples;
std::vector<Int16> chunkSamples; std::vector<Int16> chunkSamples;
std::mutex bufferLock; std::mutex bufferLock;
@ -34,7 +36,6 @@ namespace Nz
std::thread thread; std::thread thread;
UInt64 playingOffset; UInt64 playingOffset;
bool loop = false; bool loop = false;
bool streaming = false;
unsigned int sampleRate; unsigned int sampleRate;
}; };
@ -304,9 +305,22 @@ namespace Nz
} }
else else
{ {
// Starting streaming's thread std::mutex mutex;
std::condition_variable cv;
// Starting streaming thread
m_impl->streaming = true; m_impl->streaming = true;
m_impl->thread = std::thread(&Music::MusicThread, this);
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));
// Wait until thread signal it has properly started (or an error occurred)
cv.wait(lock);
if (exceptionPtr)
std::rethrow_exception(exceptionPtr);
} }
} }
@ -387,20 +401,60 @@ namespace Nz
return sampleRead != sampleCount; // End of stream (Does not happen when looping) return sampleRead != sampleCount; // End of stream (Does not happen when looping)
} }
void Music::MusicThread() void Music::MusicThread(std::condition_variable& cv, std::mutex& m, std::exception_ptr& err)
{ {
// Allocation of streaming buffers // Allocation of streaming buffers
ALuint buffers[NAZARA_AUDIO_STREAMED_BUFFER_COUNT]; std::array<ALuint, NAZARA_AUDIO_STREAMED_BUFFER_COUNT> buffers;
alGenBuffers(NAZARA_AUDIO_STREAMED_BUFFER_COUNT, buffers); alGenBuffers(NAZARA_AUDIO_STREAMED_BUFFER_COUNT, buffers.data());
for (unsigned int buffer : buffers) CallOnExit freebuffers([&]
{ {
if (FillAndQueueBuffer(buffer)) alDeleteBuffers(NAZARA_AUDIO_STREAMED_BUFFER_COUNT, buffers.data());
break; // We have reached the end of the stream, there is no use to add new buffers });
try
{
for (ALuint buffer : buffers)
{
if (FillAndQueueBuffer(buffer))
break; // We have reached the end of the stream, there is no use to add new buffers
}
}
catch (const std::exception&)
{
err = std::current_exception();
std::unique_lock<std::mutex> lock(m);
cv.notify_all();
return;
} }
CallOnExit unqueueBuffers([&]
{
// We delete buffers from the stream
ALint queuedBufferCount;
alGetSourcei(m_source, AL_BUFFERS_QUEUED, &queuedBufferCount);
ALuint buffer;
for (ALint i = 0; i < queuedBufferCount; ++i)
alSourceUnqueueBuffers(m_source, 1, &buffer);
});
alSourcePlay(m_source); alSourcePlay(m_source);
CallOnExit stopSource([&]
{
// Stop playing of the sound (in the case where it has not been already done)
alSourceStop(m_source);
});
// Signal we're good
{
std::unique_lock<std::mutex> lock(m);
cv.notify_all();
} // m & cv no longer exists from here
// Reading loop (Filling new buffers as playing) // Reading loop (Filling new buffers as playing)
while (m_impl->streaming) while (m_impl->streaming)
{ {
@ -438,19 +492,6 @@ namespace Nz
// We go back to sleep // We go back to sleep
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
} }
// Stop playing of the sound (in the case where it has not been already done)
alSourceStop(m_source);
// We delete buffers from the stream
ALint queuedBufferCount;
alGetSourcei(m_source, AL_BUFFERS_QUEUED, &queuedBufferCount);
ALuint buffer;
for (ALint i = 0; i < queuedBufferCount; ++i)
alSourceUnqueueBuffers(m_source, 1, &buffer);
alDeleteBuffers(NAZARA_AUDIO_STREAMED_BUFFER_COUNT, buffers);
} }
void Music::StopThread() void Music::StopThread()