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,
],"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,

View File

@ -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)

View File

@ -48,8 +48,10 @@ extern "C"
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);