Use fixed latency offset in SDL mixer. This should fix score tick sounds having uneven spacing.

Reduce buffer to 512 samples again.
This commit is contained in:
elasota
2021-04-10 21:54:00 -04:00
parent 2234190f24
commit 7ae31bc7fc

View File

@@ -17,9 +17,15 @@
#include <string.h> #include <string.h>
#include <new> #include <new>
#include <stdio.h> #include <stdio.h>
#include <chrono>
class GpAudioDriver_SDL2; class GpAudioDriver_SDL2;
typedef std::chrono::high_resolution_clock::duration GpAudioDriver_SDL2_Duration_t;
typedef std::chrono::high_resolution_clock::time_point GpAudioDriver_SDL2_TimePoint_t;
typedef std::chrono::high_resolution_clock::period GpAudioDriver_SDL2_Period_t;
typedef std::chrono::high_resolution_clock::rep GpAudioDriver_SDL2_Rep_t;
static void *AlignedAlloc(size_t size, size_t alignment) static void *AlignedAlloc(size_t size, size_t alignment)
{ {
void *storage = malloc(size + alignment); void *storage = malloc(size + alignment);
@@ -62,19 +68,12 @@ struct GpAudioChannelBufferChain_SDL2 final
bool m_hasTrigger; bool m_hasTrigger;
}; };
class GP_ALIGNED(GP_SYSTEM_MEMORY_ALIGNMENT) GpAudioChannel_SDL2 final : public IGpAudioChannel class GpAudioChannel_SDL2 final : public IGpAudioChannel
{ {
public: public:
enum ChannelState
{
ChannelState_Idle,
ChannelState_Playing,
ChannelState_Stopped,
};
friend class GpAudioDriver_SDL2; friend class GpAudioDriver_SDL2;
GpAudioChannel_SDL2(); GpAudioChannel_SDL2(GpAudioDriver_SDL2_Duration_t latency, GpAudioDriver_SDL2_Duration_t bufferTime, size_t bufferSamplesMax, uint16_t sampleRate);
~GpAudioChannel_SDL2(); ~GpAudioChannel_SDL2();
void AddRef(); void AddRef();
@@ -85,9 +84,9 @@ public:
void Stop() override; void Stop() override;
void Destroy() override; void Destroy() override;
void Consume(uint8_t *output, size_t sz); void Consume(uint8_t *output, size_t sz, GpAudioDriver_SDL2_TimePoint_t mixStartTime, GpAudioDriver_SDL2_TimePoint_t mixEndTime);
static GpAudioChannel_SDL2 *Alloc(GpAudioDriver_SDL2 *driver); static GpAudioChannel_SDL2 *Alloc(GpAudioDriver_SDL2 *driver, GpAudioDriver_SDL2_Duration_t latency, GpAudioDriver_SDL2_Duration_t bufferTime, size_t bufferSamplesMax, uint16_t sampleRate);
private: private:
bool Init(GpAudioDriver_SDL2 *driver); bool Init(GpAudioDriver_SDL2 *driver);
@@ -101,10 +100,17 @@ private:
GpAudioChannelBufferChain_SDL2 *m_firstPendingBuffer; GpAudioChannelBufferChain_SDL2 *m_firstPendingBuffer;
GpAudioChannelBufferChain_SDL2 *m_lastPendingBuffer; GpAudioChannelBufferChain_SDL2 *m_lastPendingBuffer;
ChannelState m_channelState; GpAudioDriver_SDL2_TimePoint_t m_timestamp; // Time that audio will be consumed if posted to the channel, if m_hasTimestamp is true.
GpAudioDriver_SDL2_Duration_t m_latency;
GpAudioDriver_SDL2_Duration_t m_bufferTime;
size_t m_bufferSamplesMax;
size_t m_leadingSilence;
uint16_t m_sampleRate;
bool m_isMixing;
bool m_hasTimestamp;
}; };
class GP_ALIGNED(GP_SYSTEM_MEMORY_ALIGNMENT) GpAudioDriver_SDL2 final : public IGpAudioDriver, public IGpPrefsHandler class GpAudioDriver_SDL2 final : public IGpAudioDriver, public IGpPrefsHandler
{ {
public: public:
friend class GpAudioChannel_SDL2; friend class GpAudioChannel_SDL2;
@@ -122,13 +128,15 @@ public:
bool Init(); bool Init();
static GpAudioDriver_SDL2_TimePoint_t GetCurrentTime();
private: private:
void DetachAudioChannel(GpAudioChannel_SDL2 *channel); void DetachAudioChannel(GpAudioChannel_SDL2 *channel);
static void SDLCALL StaticMixAudio(void *userdata, Uint8 *stream, int len); static void SDLCALL StaticMixAudio(void *userdata, Uint8 *stream, int len);
void MixAudio(void *stream, size_t len); void MixAudio(void *stream, size_t len);
void RefillMixChunk(GpAudioChannel_SDL2 *const*channels, size_t numChannels); void RefillMixChunk(GpAudioChannel_SDL2 *const*channels, size_t numChannels, size_t maxSamplesToFill, GpAudioDriver_SDL2_TimePoint_t mixStartTime, GpAudioDriver_SDL2_TimePoint_t mixEndTime);
GpAudioDriverProperties m_properties; GpAudioDriverProperties m_properties;
IGpMutex *m_mutex; IGpMutex *m_mutex;
@@ -141,6 +149,11 @@ private:
GpAudioChannel_SDL2 *m_channels[kMaxChannels]; GpAudioChannel_SDL2 *m_channels[kMaxChannels];
size_t m_numChannels; size_t m_numChannels;
unsigned int m_sampleRate;
GpAudioDriver_SDL2_Duration_t m_latency;
GpAudioDriver_SDL2_Duration_t m_bufferTime;
size_t m_bufferSamples;
bool m_sdlAudioRunning; bool m_sdlAudioRunning;
GP_ALIGNED(GP_SYSTEM_MEMORY_ALIGNMENT) int16_t m_mixChunk[kMixChunkSize]; GP_ALIGNED(GP_SYSTEM_MEMORY_ALIGNMENT) int16_t m_mixChunk[kMixChunkSize];
@@ -173,12 +186,19 @@ void GpAudioChannelBufferChain_SDL2::Release()
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// GpAudioChannel // GpAudioChannel
GpAudioChannel_SDL2::GpAudioChannel_SDL2() GpAudioChannel_SDL2::GpAudioChannel_SDL2(GpAudioDriver_SDL2_Duration_t latency, GpAudioDriver_SDL2_Duration_t bufferTime, size_t bufferSamplesMax, uint16_t sampleRate)
: m_callbacks(nullptr) : m_callbacks(nullptr)
, m_mutex(nullptr) , m_mutex(nullptr)
, m_owner(nullptr) , m_owner(nullptr)
, m_firstPendingBuffer(nullptr) , m_firstPendingBuffer(nullptr)
, m_lastPendingBuffer(nullptr) , m_lastPendingBuffer(nullptr)
, m_latency(latency)
, m_bufferTime(bufferTime)
, m_bufferSamplesMax(bufferSamplesMax)
, m_leadingSilence(0)
, m_sampleRate(sampleRate)
, m_isMixing(false)
, m_hasTimestamp(false)
{ {
SDL_AtomicSet(&m_refCount, 1); SDL_AtomicSet(&m_refCount, 1);
} }
@@ -222,6 +242,26 @@ void GpAudioChannel_SDL2::PostBuffer(const void *buffer, size_t bufferSize)
{ {
m_mutex->Lock(); m_mutex->Lock();
size_t leadingSilence = 0;
if (m_firstPendingBuffer == nullptr && m_hasTimestamp && !m_isMixing)
{
GpAudioDriver_SDL2_TimePoint_t queueTime = GpAudioDriver_SDL2::GetCurrentTime() + m_latency;
if (queueTime > m_timestamp)
{
const GpAudioDriver_SDL2_Duration_t leadTime = queueTime - m_timestamp;
if (leadTime > m_bufferTime)
leadingSilence = m_bufferSamplesMax;
else
{
const GpAudioDriver_SDL2_Rep_t leadTimeRep = leadTime.count();
leadingSilence = leadTimeRep * static_cast<GpAudioDriver_SDL2_Rep_t>(m_sampleRate) * GpAudioDriver_SDL2_Period_t::num / GpAudioDriver_SDL2_Period_t::den;
}
}
}
m_leadingSilence = leadingSilence;
while (bufferSize > 0) while (bufferSize > 0)
{ {
GpAudioChannelBufferChain_SDL2 *newBuffer = GpAudioChannelBufferChain_SDL2::Alloc(); GpAudioChannelBufferChain_SDL2 *newBuffer = GpAudioChannelBufferChain_SDL2::Alloc();
@@ -289,10 +329,36 @@ bool GpAudioChannel_SDL2::Init(GpAudioDriver_SDL2 *driver)
return true; return true;
} }
void GpAudioChannel_SDL2::Consume(uint8_t *output, size_t sz) void GpAudioChannel_SDL2::Consume(uint8_t *output, size_t sz, GpAudioDriver_SDL2_TimePoint_t mixStartTime, GpAudioDriver_SDL2_TimePoint_t mixEndTime)
{ {
m_mutex->Lock(); m_mutex->Lock();
m_isMixing = true;
m_hasTimestamp = true;
m_timestamp = mixEndTime;
if (sz <= m_leadingSilence)
{
memset(output, 0x80, sz);
m_leadingSilence -= sz;
m_isMixing = false;
m_mutex->Unlock();
return;
}
else
{
size_t leadingSilence = m_leadingSilence;
if (leadingSilence > 0)
{
memset(output, 0x80, leadingSilence);
output += leadingSilence;
sz -= leadingSilence;
m_leadingSilence = 0;
}
}
while (m_firstPendingBuffer != nullptr) while (m_firstPendingBuffer != nullptr)
{ {
GpAudioChannelBufferChain_SDL2 *buffer = m_firstPendingBuffer; GpAudioChannelBufferChain_SDL2 *buffer = m_firstPendingBuffer;
@@ -325,18 +391,20 @@ void GpAudioChannel_SDL2::Consume(uint8_t *output, size_t sz)
} }
} }
m_isMixing = false;
m_mutex->Unlock(); m_mutex->Unlock();
memset(output, 0x80, sz); memset(output, 0x80, sz);
} }
GpAudioChannel_SDL2 *GpAudioChannel_SDL2::Alloc(GpAudioDriver_SDL2 *driver) GpAudioChannel_SDL2 *GpAudioChannel_SDL2::Alloc(GpAudioDriver_SDL2 *driver, GpAudioDriver_SDL2_Duration_t latency, GpAudioDriver_SDL2_Duration_t bufferTime, size_t bufferSamplesMax, uint16_t sampleRate)
{ {
void *storage = AlignedAlloc(sizeof(GpAudioChannel_SDL2), GP_SYSTEM_MEMORY_ALIGNMENT); void *storage = AlignedAlloc(sizeof(GpAudioChannel_SDL2), GP_SYSTEM_MEMORY_ALIGNMENT);
if (!storage) if (!storage)
return nullptr; return nullptr;
GpAudioChannel_SDL2 *channel = new (storage) GpAudioChannel_SDL2(); GpAudioChannel_SDL2 *channel = new (storage) GpAudioChannel_SDL2(latency, bufferTime, bufferSamplesMax, sampleRate);
if (!channel->Init(driver)) if (!channel->Init(driver))
{ {
channel->Destroy(); channel->Destroy();
@@ -354,6 +422,9 @@ GpAudioDriver_SDL2::GpAudioDriver_SDL2(const GpAudioDriverProperties &properties
: m_properties(properties) : m_properties(properties)
, m_mutex(nullptr) , m_mutex(nullptr)
, m_numChannels(0) , m_numChannels(0)
, m_sampleRate(0)
, m_latency(GpAudioDriver_SDL2_Duration_t::zero())
, m_bufferTime(GpAudioDriver_SDL2_Duration_t::zero())
, m_sdlAudioRunning(false) , m_sdlAudioRunning(false)
, m_mixChunkReadOffset(kMixChunkSize) , m_mixChunkReadOffset(kMixChunkSize)
, m_audioVolumeScale(kMaxAudioVolumeScale) , m_audioVolumeScale(kMaxAudioVolumeScale)
@@ -377,7 +448,7 @@ GpAudioDriver_SDL2::~GpAudioDriver_SDL2()
IGpAudioChannel *GpAudioDriver_SDL2::CreateChannel() IGpAudioChannel *GpAudioDriver_SDL2::CreateChannel()
{ {
GpAudioChannel_SDL2 *newChannel = GpAudioChannel_SDL2::Alloc(this); GpAudioChannel_SDL2 *newChannel = GpAudioChannel_SDL2::Alloc(this, m_latency, m_bufferTime, m_bufferSamples, m_sampleRate);
if (!newChannel) if (!newChannel)
return nullptr; return nullptr;
@@ -437,7 +508,7 @@ bool GpAudioDriver_SDL2::Init()
requestedSpec.channels = 1; requestedSpec.channels = 1;
requestedSpec.format = AUDIO_S16; requestedSpec.format = AUDIO_S16;
requestedSpec.freq = m_properties.m_sampleRate; requestedSpec.freq = m_properties.m_sampleRate;
requestedSpec.samples = 1024; requestedSpec.samples = 512;
requestedSpec.userdata = this; requestedSpec.userdata = this;
if (SDL_OpenAudio(&requestedSpec, nullptr)) if (SDL_OpenAudio(&requestedSpec, nullptr))
@@ -450,6 +521,10 @@ bool GpAudioDriver_SDL2::Init()
SDL_PauseAudio(0); SDL_PauseAudio(0);
m_sdlAudioRunning = true; m_sdlAudioRunning = true;
m_sampleRate = requestedSpec.freq;
m_latency = GpAudioDriver_SDL2_Duration_t(static_cast<GpAudioDriver_SDL2_Rep_t>(GpAudioDriver_SDL2_Period_t::den * requestedSpec.samples / GpAudioDriver_SDL2_Period_t::num / m_sampleRate));
m_bufferTime = GpAudioDriver_SDL2_Duration_t(static_cast<GpAudioDriver_SDL2_Rep_t>(GpAudioDriver_SDL2_Period_t::den * requestedSpec.samples / GpAudioDriver_SDL2_Period_t::num / m_sampleRate));
m_bufferSamples = requestedSpec.samples;
return true; return true;
} }
@@ -492,8 +567,13 @@ void GpAudioDriver_SDL2::MixAudio(void *stream, size_t len)
} }
m_mutex->Unlock(); m_mutex->Unlock();
size_t samplesRemaining = len / sizeof(int16_t); const size_t totalSamples = len / sizeof(int16_t);
size_t samplesRemaining = totalSamples;
GpAudioDriver_SDL2_TimePoint_t audioMixStartTime = GpAudioDriver_SDL2::GetCurrentTime();
GpAudioDriver_SDL2_TimePoint_t audioMixBlockStartTime = audioMixStartTime;
size_t samplesSinceStart = 0;
for (;;) for (;;)
{ {
size_t availableInMixChunk = kMixChunkSize - m_mixChunkReadOffset; size_t availableInMixChunk = kMixChunkSize - m_mixChunkReadOffset;
@@ -510,10 +590,17 @@ void GpAudioDriver_SDL2::MixAudio(void *stream, size_t len)
memcpy(stream, m_mixChunk + m_mixChunkReadOffset, availableInMixChunk * sizeof(int16_t)); memcpy(stream, m_mixChunk + m_mixChunkReadOffset, availableInMixChunk * sizeof(int16_t));
stream = static_cast<int16_t*>(stream) + availableInMixChunk; stream = static_cast<int16_t*>(stream) + availableInMixChunk;
samplesRemaining -= availableInMixChunk;
samplesSinceStart += availableInMixChunk;
GpAudioDriver_SDL2_Duration_t audioMixDurationSinceStart = GpAudioDriver_SDL2_Duration_t(static_cast<GpAudioDriver_SDL2_Rep_t>(GpAudioDriver_SDL2_Period_t::den * samplesSinceStart / GpAudioDriver_SDL2_Period_t::num / m_sampleRate));
GpAudioDriver_SDL2_TimePoint_t audioMixBlockEndTime = audioMixStartTime + audioMixDurationSinceStart;
m_mixChunkReadOffset = 0; m_mixChunkReadOffset = 0;
RefillMixChunk(mixingChannels, numChannels); RefillMixChunk(mixingChannels, numChannels, samplesRemaining, audioMixBlockStartTime, audioMixBlockEndTime);
audioMixBlockStartTime = audioMixBlockEndTime;
samplesRemaining -= availableInMixChunk;
} }
} }
@@ -521,7 +608,7 @@ void GpAudioDriver_SDL2::MixAudio(void *stream, size_t len)
mixingChannels[i]->Release(); mixingChannels[i]->Release();
} }
void GpAudioDriver_SDL2::RefillMixChunk(GpAudioChannel_SDL2 *const*channels, size_t numChannels) void GpAudioDriver_SDL2::RefillMixChunk(GpAudioChannel_SDL2 *const*channels, size_t numChannels, size_t maxSamplesToFill, GpAudioDriver_SDL2_TimePoint_t mixStartTime, GpAudioDriver_SDL2_TimePoint_t mixEndTime)
{ {
uint8_t audioMixBufferUnaligned[kMixChunkSize + GP_SYSTEM_MEMORY_ALIGNMENT]; uint8_t audioMixBufferUnaligned[kMixChunkSize + GP_SYSTEM_MEMORY_ALIGNMENT];
uint8_t *audioMixBuffer = audioMixBufferUnaligned; uint8_t *audioMixBuffer = audioMixBufferUnaligned;
@@ -536,28 +623,44 @@ void GpAudioDriver_SDL2::RefillMixChunk(GpAudioChannel_SDL2 *const*channels, siz
const int16_t audioVolumeScale = m_audioVolumeScale; const int16_t audioVolumeScale = m_audioVolumeScale;
size_t samplesToFill = kMixChunkSize;
if (samplesToFill > maxSamplesToFill)
{
m_mixChunkReadOffset += samplesToFill - maxSamplesToFill;
samplesToFill = maxSamplesToFill;
}
else
m_mixChunkReadOffset = 0;
int16_t *mixChunkStart = m_mixChunk + m_mixChunkReadOffset;
for (size_t i = 0; i < numChannels; i++) for (size_t i = 0; i < numChannels; i++)
{ {
channels[i]->Consume(audioMixBuffer, kMixChunkSize); channels[i]->Consume(audioMixBuffer, samplesToFill, mixStartTime, mixEndTime);
if (i == 0) if (i == 0)
{ {
noAudio = false; noAudio = false;
for (size_t j = 0; j < kMixChunkSize; j++) for (size_t j = 0; j < samplesToFill; j++)
m_mixChunk[j] = (static_cast<int16_t>(audioMixBuffer[j]) - 0x80) * audioVolumeScale; mixChunkStart[j] = (static_cast<int16_t>(audioMixBuffer[j]) - 0x80) * audioVolumeScale;
} }
else else
{ {
for (size_t j = 0; j < kMixChunkSize; j++) for (size_t j = 0; j < samplesToFill; j++)
m_mixChunk[j] += (static_cast<int16_t>(audioMixBuffer[j]) - 0x80) * audioVolumeScale; mixChunkStart[j] += (static_cast<int16_t>(audioMixBuffer[j]) - 0x80) * audioVolumeScale;
} }
} }
if (noAudio) if (noAudio)
memset(m_mixChunk, 0, kMixChunkSize * sizeof(m_mixChunk[0])); memset(mixChunkStart, 0, samplesToFill * sizeof(mixChunkStart[0]));
} }
GpAudioDriver_SDL2_TimePoint_t GpAudioDriver_SDL2::GetCurrentTime()
{
return std::chrono::high_resolution_clock::now();
}
IGpAudioDriver *GpDriver_CreateAudioDriver_SDL(const GpAudioDriverProperties &properties) IGpAudioDriver *GpDriver_CreateAudioDriver_SDL(const GpAudioDriverProperties &properties)
{ {
void *storage = AlignedAlloc(sizeof(GpAudioDriver_SDL2), GP_SYSTEM_MEMORY_ALIGNMENT); void *storage = AlignedAlloc(sizeof(GpAudioDriver_SDL2), GP_SYSTEM_MEMORY_ALIGNMENT);