streaming sounds triple buffer to avoid micro stutter

main
cosmonaut 2021-11-18 12:13:57 -08:00
parent c6e3879a67
commit e17d7e0da8
4 changed files with 110 additions and 70 deletions

Binary file not shown.

View File

@ -55,8 +55,9 @@
2, 2,
2, 2,
],"resourceVersion":"1.0","name":"FAudioGMS_SetListenerPosition","tags":[],"resourceType":"GMExtensionFunction",}, ],"resourceVersion":"1.0","name":"FAudioGMS_SetListenerPosition","tags":[],"resourceType":"GMExtensionFunction",},
{"externalName":"FAudioGMS_StreamingSound_LoadOGG","kind":1,"help":"FAudioGMS_StreamingSound_LoadOGG(filePath)","hidden":false,"returnType":2,"argCount":0,"args":[ {"externalName":"FAudioGMS_StreamingSound_LoadOGG","kind":1,"help":"FAudioGMS_StreamingSound_LoadOGG(filePath, bufferSizeInBytes)","hidden":false,"returnType":2,"argCount":0,"args":[
1, 1,
2,
],"resourceVersion":"1.0","name":"FAudioGMS_StreamingSound_LoadOGG","tags":[],"resourceType":"GMExtensionFunction",}, ],"resourceVersion":"1.0","name":"FAudioGMS_StreamingSound_LoadOGG","tags":[],"resourceType":"GMExtensionFunction",},
{"externalName":"FAudioGMS_SoundInstance_SetPan","kind":1,"help":"FAudioGMS_SoundInstance_SetPan(soundInstanceID, pan)","hidden":false,"returnType":2,"argCount":0,"args":[ {"externalName":"FAudioGMS_SoundInstance_SetPan","kind":1,"help":"FAudioGMS_SoundInstance_SetPan(soundInstanceID, pan)","hidden":false,"returnType":2,"argCount":0,"args":[
2, 2,

View File

@ -174,13 +174,19 @@ typedef struct FAudioGMS_StaticSound
uint32_t lengthInSeconds; uint32_t lengthInSeconds;
} FAudioGMS_StaticSound; } FAudioGMS_StaticSound;
/* if we don't triple buffer, we'll microstutter the voice while waiting for decode */
#define STREAMING_BUFFER_COUNT 3
#define DEFAULT_STREAMING_BUFFER_SIZE 32768
typedef struct FAudioGMS_StreamingSound typedef struct FAudioGMS_StreamingSound
{ {
stb_vorbis *fileHandle; stb_vorbis *fileHandle;
stb_vorbis_info info; stb_vorbis_info info;
float *streamBuffer; float *streamBuffer[STREAMING_BUFFER_COUNT];
uint32_t streamBufferSize; uint32_t streamBufferSize;
uint32_t mostRecentBufferOffset; /* used for calculating track position */ uint32_t nextStreamBufferIndex; /* it's a ring buffer! */
uint32_t buffersLoadedCount; /* how many buffers are we buffering? */
uint32_t mostRecentSampleOffset; /* used for calculating track position */
uint8_t isFinalBuffer; /* used to detect end of playback */ uint8_t isFinalBuffer; /* used to detect end of playback */
} FAudioGMS_StreamingSound; } FAudioGMS_StreamingSound;
@ -321,7 +327,7 @@ static inline FAudioGMS_EffectChain *FAudioGMS_INTERNAL_LookupEffectChain(uint32
/* Forward declarations to avoid some annoying BS */ /* Forward declarations to avoid some annoying BS */
static void FAudioGMS_INTERNAL_SoundInstance_AddBuffer(FAudioGMS_SoundInstance *instance); static void FAudioGMS_INTERNAL_SoundInstance_AddBuffers(FAudioGMS_SoundInstance *instance);
static void FAudioGMS_INTERNAL_SoundInstance_Play(FAudioGMS_SoundInstance *instance); static void FAudioGMS_INTERNAL_SoundInstance_Play(FAudioGMS_SoundInstance *instance);
static void FAudioGMS_INTERNAL_SoundInstance_Stop(FAudioGMS_SoundInstance *instance); static void FAudioGMS_INTERNAL_SoundInstance_Stop(FAudioGMS_SoundInstance *instance);
static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance *instance); static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance *instance);
@ -345,13 +351,22 @@ static void FAudioGMS_INTERNAL_OnBufferEndCallback(
} }
else else
{ {
instance->soundData.streamingSound.buffersLoadedCount -= 1;
instance->soundData.streamingSound.mostRecentSampleOffset +=
instance->soundData.streamingSound.streamBufferSize /
(instance->format.nChannels * sizeof(float));
instance->soundData.streamingSound.mostRecentSampleOffset = SDL_min(
instance->soundData.streamingSound.mostRecentSampleOffset,
instance->playLength);
if (instance->soundData.streamingSound.isFinalBuffer) if (instance->soundData.streamingSound.isFinalBuffer)
{ {
FAudioGMS_INTERNAL_SoundInstance_Stop(instance); FAudioGMS_INTERNAL_SoundInstance_Stop(instance);
} }
else else
{ {
FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance); FAudioGMS_INTERNAL_SoundInstance_AddBuffers(instance);
} }
} }
@ -971,71 +986,73 @@ static void FAudioGMS_INTERNAL_Apply3D(FAudioGMS_SoundInstance *instance)
0); 0);
} }
static void FAudioGMS_INTERNAL_SoundInstance_AddBuffer(FAudioGMS_SoundInstance *instance) static void FAudioGMS_INTERNAL_SoundInstance_AddBuffers(FAudioGMS_SoundInstance *instance)
{ {
uint32_t defaultRequestedSampleCount = instance->format.nSamplesPerSec / 10; /* FIXME: make this configurable */ uint32_t i;
uint32_t requestedSampleCount = defaultRequestedSampleCount; for (i = 0; i < STREAMING_BUFFER_COUNT - instance->soundData.streamingSound.buffersLoadedCount; i += 1)
if (instance->playLength != 0)
{ {
uint32_t distanceToEndPoint = uint32_t defaultRequestedSampleCount = instance->soundData.streamingSound.streamBufferSize /
(instance->playBegin + instance->playLength) - (instance->format.nChannels * sizeof(float));
stb_vorbis_get_sample_offset(instance->soundData.streamingSound.fileHandle); uint32_t requestedSampleCount = defaultRequestedSampleCount;
requestedSampleCount = SDL_min(requestedSampleCount, distanceToEndPoint);
}
uint32_t requiredStagingBufferSize = if (instance->playLength != 0)
requestedSampleCount * instance->format.nChannels * sizeof(float);
if (instance->soundData.streamingSound.streamBufferSize < requiredStagingBufferSize)
{
instance->soundData.streamingSound.streamBuffer =
SDL_realloc(instance->soundData.streamingSound.streamBuffer, requiredStagingBufferSize);
instance->soundData.streamingSound.streamBufferSize = requiredStagingBufferSize;
}
instance->soundData.streamingSound.mostRecentBufferOffset =
stb_vorbis_get_sample_offset(instance->soundData.streamingSound.fileHandle);
/* NOTE: this function returns samples per channel, not total samples */
uint32_t sampleCount = stb_vorbis_get_samples_float_interleaved(
instance->soundData.streamingSound.fileHandle,
instance->format.nChannels,
instance->soundData.streamingSound.streamBuffer,
requestedSampleCount * instance->format.nChannels);
FAudioBuffer buffer;
buffer.AudioBytes = sampleCount * instance->format.nChannels * sizeof(float);
buffer.pAudioData = (uint8_t *)instance->soundData.streamingSound.streamBuffer;
buffer.PlayBegin = 0;
buffer.PlayLength = sampleCount;
buffer.Flags = 0;
buffer.LoopBegin = 0;
buffer.LoopCount = 0;
buffer.LoopLength = 0;
buffer.pContext = instance; /* context for OnBufferEnd callback */
FAudioSourceVoice_SubmitSourceBuffer(instance->voice.handle, &buffer, NULL);
instance->soundData.streamingSound.isFinalBuffer = 0;
if (sampleCount < defaultRequestedSampleCount)
{
if (instance->loop)
{ {
stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, instance->playBegin); uint32_t distanceToEndPoint =
(instance->playBegin + instance->playLength) -
stb_vorbis_get_sample_offset(instance->soundData.streamingSound.fileHandle);
requestedSampleCount = SDL_min(requestedSampleCount, distanceToEndPoint);
} }
else
/* NOTE: this function returns samples per channel, not total samples */
uint32_t sampleCount = stb_vorbis_get_samples_float_interleaved(
instance->soundData.streamingSound.fileHandle,
instance->format.nChannels,
instance->soundData.streamingSound
.streamBuffer[instance->soundData.streamingSound.nextStreamBufferIndex],
requestedSampleCount * instance->format.nChannels);
FAudioBuffer buffer;
buffer.AudioBytes = sampleCount * instance->format.nChannels * sizeof(float);
buffer.pAudioData =
(uint8_t *)instance->soundData.streamingSound
.streamBuffer[instance->soundData.streamingSound.nextStreamBufferIndex];
buffer.PlayBegin = 0;
buffer.PlayLength = sampleCount;
buffer.Flags = 0;
buffer.LoopBegin = 0;
buffer.LoopCount = 0;
buffer.LoopLength = 0;
buffer.pContext = instance; /* context for OnBufferEnd callback */
FAudioSourceVoice_SubmitSourceBuffer(instance->voice.handle, &buffer, NULL);
instance->soundData.streamingSound.isFinalBuffer = 0;
if (sampleCount < defaultRequestedSampleCount)
{ {
instance->soundData.streamingSound.isFinalBuffer = 1; if (instance->loop)
{
stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, instance->playBegin);
}
else
{
instance->soundData.streamingSound.isFinalBuffer = 1;
}
} }
instance->soundData.streamingSound.nextStreamBufferIndex =
(instance->soundData.streamingSound.nextStreamBufferIndex + 1) % STREAMING_BUFFER_COUNT;
instance->soundData.streamingSound.buffersLoadedCount += 1;
} }
} }
double FAudioGMS_StreamingSound_LoadOGG(char *filePath) double FAudioGMS_StreamingSound_LoadOGG(char *filePath, double bufferSizeInBytes)
{ {
RETURN_ON_NULL_DEVICE(-1.0) RETURN_ON_NULL_DEVICE(-1.0)
uint32_t i;
int error = 0; int error = 0;
uint32_t bufferSizeInBytesInt = (uint32_t)bufferSizeInBytes;
stb_vorbis *fileHandle = stb_vorbis_open_filename(filePath, &error, NULL); stb_vorbis *fileHandle = stb_vorbis_open_filename(filePath, &error, NULL);
if (error != 0) if (error != 0)
@ -1045,6 +1062,11 @@ double FAudioGMS_StreamingSound_LoadOGG(char *filePath)
return -1; return -1;
} }
if (bufferSizeInBytesInt == 0)
{
bufferSizeInBytesInt = DEFAULT_STREAMING_BUFFER_SIZE; /* A reasonable default! */
}
stb_vorbis_info info = stb_vorbis_get_info(fileHandle); stb_vorbis_info info = stb_vorbis_get_info(fileHandle);
FAudioGMS_SoundInstance *instance = FAudioGMS_SoundInstance *instance =
@ -1052,14 +1074,20 @@ double FAudioGMS_StreamingSound_LoadOGG(char *filePath)
instance->soundData.streamingSound.fileHandle = fileHandle; instance->soundData.streamingSound.fileHandle = fileHandle;
instance->soundData.streamingSound.info = info; instance->soundData.streamingSound.info = info;
instance->soundData.streamingSound.streamBuffer = NULL; for (i = 0; i < STREAMING_BUFFER_COUNT; i += 1)
instance->soundData.streamingSound.streamBufferSize = 0; {
instance->soundData.streamingSound.mostRecentBufferOffset = 0; instance->soundData.streamingSound.streamBuffer[i] = SDL_malloc(bufferSizeInBytesInt);
}
instance->soundData.streamingSound.streamBufferSize = bufferSizeInBytesInt;
instance->soundData.streamingSound.mostRecentSampleOffset = 0;
instance->soundData.streamingSound.isFinalBuffer = 0; instance->soundData.streamingSound.isFinalBuffer = 0;
instance->soundData.streamingSound.nextStreamBufferIndex = 0;
instance->soundData.streamingSound.buffersLoadedCount = 0;
instance->playLength = stb_vorbis_stream_length_in_samples(fileHandle); instance->playLength = stb_vorbis_stream_length_in_samples(fileHandle);
FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance); FAudioGMS_INTERNAL_SoundInstance_AddBuffers(instance);
return instance->id; return instance->id;
} }
@ -1212,10 +1240,12 @@ static void FAudioGMS_INTERNAL_SoundInstance_Stop(FAudioGMS_SoundInstance *insta
if (!instance->isStatic) if (!instance->isStatic)
{ {
instance->soundData.streamingSound.buffersLoadedCount = 0;
stb_vorbis_seek( stb_vorbis_seek(
instance->soundData.streamingSound.fileHandle, instance->soundData.streamingSound.fileHandle,
instance->playBegin); /* back to the start */ instance->playBegin); /* back to the start */
FAudioGMS_INTERNAL_SoundInstance_AddBuffer( FAudioGMS_INTERNAL_SoundInstance_AddBuffers(
instance); /* preload so we dont stutter on play */ instance); /* preload so we dont stutter on play */
} }
} }
@ -1365,8 +1395,9 @@ void FAudioGMS_SoundInstance_SetTrackPositionInSeconds(
} }
else else
{ {
instance->soundData.streamingSound.buffersLoadedCount = 0;
stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, sampleFrame); stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, sampleFrame);
FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance); FAudioGMS_INTERNAL_SoundInstance_AddBuffers(instance);
} }
if (currentState == SoundState_Playing) if (currentState == SoundState_Playing)
@ -1387,7 +1418,7 @@ static uint32_t FAudioGMS_INTERNAL_SoundInstance_GetTrackPositionInSampleFrames(
} }
else else
{ {
return instance->soundData.streamingSound.mostRecentBufferOffset + return instance->soundData.streamingSound.mostRecentSampleOffset +
instance->voice.handle->src.curBufferOffset; instance->voice.handle->src.curBufferOffset;
} }
} }
@ -1436,8 +1467,9 @@ void FAudioGMS_SoundInstance_SetPlayRegion(
else if (!instance->isStatic) else if (!instance->isStatic)
{ {
FAudioSourceVoice_FlushSourceBuffers(instance->voice.handle); FAudioSourceVoice_FlushSourceBuffers(instance->voice.handle);
instance->soundData.streamingSound.buffersLoadedCount = 0;
stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, instance->playBegin); stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, instance->playBegin);
FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance); FAudioGMS_INTERNAL_SoundInstance_AddBuffers(instance);
} }
} }
else else
@ -1565,6 +1597,8 @@ void FAudioGMS_SetListenerVelocity(double xVelocity, double yVelocity, double zV
static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance *instance) static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance *instance)
{ {
uint32_t i;
if (instance != NULL) if (instance != NULL)
{ {
device->soundInstances[instance->id] = NULL; device->soundInstances[instance->id] = NULL;
@ -1576,7 +1610,10 @@ static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance *in
if (!instance->isStatic) if (!instance->isStatic)
{ {
SDL_free(instance->soundData.streamingSound.streamBuffer); for (i = 0; i < STREAMING_BUFFER_COUNT; i += 1)
{
SDL_free(instance->soundData.streamingSound.streamBuffer[i]);
}
stb_vorbis_close(instance->soundData.streamingSound.fileHandle); stb_vorbis_close(instance->soundData.streamingSound.fileHandle);
} }
if (instance->is3D) if (instance->is3D)

View File

@ -48,8 +48,10 @@ extern "C"
double staticSoundID); /* returns a sound instance ID */ double staticSoundID); /* returns a sound instance ID */
FAUDIOGMSAPI void FAudioGMS_StaticSound_Destroy(double staticSoundID); FAUDIOGMSAPI void FAudioGMS_StaticSound_Destroy(double staticSoundID);
/* returns a sound instance ID */
FAUDIOGMSAPI double FAudioGMS_StreamingSound_LoadOGG( FAUDIOGMSAPI double FAudioGMS_StreamingSound_LoadOGG(
char *filepath); /* returns a sound instance ID */ char *filepath,
double bufferSizeInBytes); /* if 0 is passed we will use a sensible default*/
FAUDIOGMSAPI void FAudioGMS_SoundInstance_Play(double soundInstanceID); FAUDIOGMSAPI void FAudioGMS_SoundInstance_Play(double soundInstanceID);
FAUDIOGMSAPI void FAudioGMS_SoundInstance_Pause(double soundInstanceID); FAUDIOGMSAPI void FAudioGMS_SoundInstance_Pause(double soundInstanceID);