From e17d7e0da8529aa358075621772efd7cea88d0d5 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 18 Nov 2021 12:13:57 -0800 Subject: [PATCH] streaming sounds triple buffer to avoid micro stutter --- gamemaker/extensions/FAudioGMS/FAudioGMS.dll | 4 +- gamemaker/extensions/FAudioGMS/FAudioGMS.yy | 3 +- src/FAudioGMS.c | 167 +++++++++++-------- src/FAudioGMS.h | 6 +- 4 files changed, 110 insertions(+), 70 deletions(-) diff --git a/gamemaker/extensions/FAudioGMS/FAudioGMS.dll b/gamemaker/extensions/FAudioGMS/FAudioGMS.dll index 95ed37c..5f2b371 100644 --- a/gamemaker/extensions/FAudioGMS/FAudioGMS.dll +++ b/gamemaker/extensions/FAudioGMS/FAudioGMS.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f2fa42a72d17721c2e3852197704f0f27b32235e1958c48e944b3c0243b438f -size 1521152 +oid sha256:cbdb1bd861a1efab3a9071392f2c7d837e6e663ae9b39447f3fbb7fe42aed689 +size 1521664 diff --git a/gamemaker/extensions/FAudioGMS/FAudioGMS.yy b/gamemaker/extensions/FAudioGMS/FAudioGMS.yy index 1d77857..9eb2290 100644 --- a/gamemaker/extensions/FAudioGMS/FAudioGMS.yy +++ b/gamemaker/extensions/FAudioGMS/FAudioGMS.yy @@ -55,8 +55,9 @@ 2, 2, ],"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, + 2, ],"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":[ 2, diff --git a/src/FAudioGMS.c b/src/FAudioGMS.c index 702d08e..6a88b41 100644 --- a/src/FAudioGMS.c +++ b/src/FAudioGMS.c @@ -174,13 +174,19 @@ typedef struct FAudioGMS_StaticSound uint32_t lengthInSeconds; } 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 { stb_vorbis *fileHandle; stb_vorbis_info info; - float *streamBuffer; + float *streamBuffer[STREAMING_BUFFER_COUNT]; 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 */ } FAudioGMS_StreamingSound; @@ -321,7 +327,7 @@ static inline FAudioGMS_EffectChain *FAudioGMS_INTERNAL_LookupEffectChain(uint32 /* 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_Stop(FAudioGMS_SoundInstance *instance); static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance *instance); @@ -345,13 +351,22 @@ static void FAudioGMS_INTERNAL_OnBufferEndCallback( } 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) { FAudioGMS_INTERNAL_SoundInstance_Stop(instance); } else { - FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance); + FAudioGMS_INTERNAL_SoundInstance_AddBuffers(instance); } } @@ -971,71 +986,73 @@ static void FAudioGMS_INTERNAL_Apply3D(FAudioGMS_SoundInstance *instance) 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 requestedSampleCount = defaultRequestedSampleCount; - - if (instance->playLength != 0) + uint32_t i; + for (i = 0; i < STREAMING_BUFFER_COUNT - instance->soundData.streamingSound.buffersLoadedCount; i += 1) { - uint32_t distanceToEndPoint = - (instance->playBegin + instance->playLength) - - stb_vorbis_get_sample_offset(instance->soundData.streamingSound.fileHandle); - requestedSampleCount = SDL_min(requestedSampleCount, distanceToEndPoint); - } + uint32_t defaultRequestedSampleCount = instance->soundData.streamingSound.streamBufferSize / + (instance->format.nChannels * sizeof(float)); + uint32_t requestedSampleCount = defaultRequestedSampleCount; - uint32_t requiredStagingBufferSize = - 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) + if (instance->playLength != 0) { - 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) + uint32_t i; int error = 0; + uint32_t bufferSizeInBytesInt = (uint32_t)bufferSizeInBytes; stb_vorbis *fileHandle = stb_vorbis_open_filename(filePath, &error, NULL); if (error != 0) @@ -1045,6 +1062,11 @@ double FAudioGMS_StreamingSound_LoadOGG(char *filePath) return -1; } + if (bufferSizeInBytesInt == 0) + { + bufferSizeInBytesInt = DEFAULT_STREAMING_BUFFER_SIZE; /* A reasonable default! */ + } + stb_vorbis_info info = stb_vorbis_get_info(fileHandle); FAudioGMS_SoundInstance *instance = @@ -1052,14 +1074,20 @@ double FAudioGMS_StreamingSound_LoadOGG(char *filePath) instance->soundData.streamingSound.fileHandle = fileHandle; instance->soundData.streamingSound.info = info; - instance->soundData.streamingSound.streamBuffer = NULL; - instance->soundData.streamingSound.streamBufferSize = 0; - instance->soundData.streamingSound.mostRecentBufferOffset = 0; + for (i = 0; i < STREAMING_BUFFER_COUNT; i += 1) + { + 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.nextStreamBufferIndex = 0; + instance->soundData.streamingSound.buffersLoadedCount = 0; instance->playLength = stb_vorbis_stream_length_in_samples(fileHandle); - FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance); + FAudioGMS_INTERNAL_SoundInstance_AddBuffers(instance); return instance->id; } @@ -1212,10 +1240,12 @@ static void FAudioGMS_INTERNAL_SoundInstance_Stop(FAudioGMS_SoundInstance *insta if (!instance->isStatic) { + instance->soundData.streamingSound.buffersLoadedCount = 0; + stb_vorbis_seek( instance->soundData.streamingSound.fileHandle, instance->playBegin); /* back to the start */ - FAudioGMS_INTERNAL_SoundInstance_AddBuffer( + FAudioGMS_INTERNAL_SoundInstance_AddBuffers( instance); /* preload so we dont stutter on play */ } } @@ -1365,8 +1395,9 @@ void FAudioGMS_SoundInstance_SetTrackPositionInSeconds( } else { + instance->soundData.streamingSound.buffersLoadedCount = 0; stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, sampleFrame); - FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance); + FAudioGMS_INTERNAL_SoundInstance_AddBuffers(instance); } if (currentState == SoundState_Playing) @@ -1387,7 +1418,7 @@ static uint32_t FAudioGMS_INTERNAL_SoundInstance_GetTrackPositionInSampleFrames( } else { - return instance->soundData.streamingSound.mostRecentBufferOffset + + return instance->soundData.streamingSound.mostRecentSampleOffset + instance->voice.handle->src.curBufferOffset; } } @@ -1436,8 +1467,9 @@ void FAudioGMS_SoundInstance_SetPlayRegion( else if (!instance->isStatic) { FAudioSourceVoice_FlushSourceBuffers(instance->voice.handle); + instance->soundData.streamingSound.buffersLoadedCount = 0; stb_vorbis_seek(instance->soundData.streamingSound.fileHandle, instance->playBegin); - FAudioGMS_INTERNAL_SoundInstance_AddBuffer(instance); + FAudioGMS_INTERNAL_SoundInstance_AddBuffers(instance); } } else @@ -1565,6 +1597,8 @@ void FAudioGMS_SetListenerVelocity(double xVelocity, double yVelocity, double zV static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance *instance) { + uint32_t i; + if (instance != NULL) { device->soundInstances[instance->id] = NULL; @@ -1576,7 +1610,10 @@ static void FAudioGMS_INTERNAL_SoundInstance_Destroy(FAudioGMS_SoundInstance *in 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); } if (instance->is3D) diff --git a/src/FAudioGMS.h b/src/FAudioGMS.h index 7b1d385..5a2504e 100644 --- a/src/FAudioGMS.h +++ b/src/FAudioGMS.h @@ -47,9 +47,11 @@ extern "C" FAUDIOGMSAPI double FAudioGMS_StaticSound_CreateSoundInstance( double staticSoundID); /* returns a sound instance ID */ FAUDIOGMSAPI void FAudioGMS_StaticSound_Destroy(double staticSoundID); - + + /* returns a sound instance ID */ 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_Pause(double soundInstanceID);