streaming sounds triple buffer to avoid micro stutter
parent
c6e3879a67
commit
e17d7e0da8
BIN
gamemaker/extensions/FAudioGMS/FAudioGMS.dll (Stored with Git LFS)
BIN
gamemaker/extensions/FAudioGMS/FAudioGMS.dll (Stored with Git LFS)
Binary file not shown.
|
@ -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,
|
||||||
|
|
167
src/FAudioGMS.c
167
src/FAudioGMS.c
|
@ -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)
|
||||||
|
|
|
@ -47,9 +47,11 @@ extern "C"
|
||||||
FAUDIOGMSAPI double FAudioGMS_StaticSound_CreateSoundInstance(
|
FAUDIOGMSAPI double FAudioGMS_StaticSound_CreateSoundInstance(
|
||||||
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);
|
||||||
|
|
Loading…
Reference in New Issue