Просмотр исходного кода

Audio: Remove use of ma_data_converter_get_required_input_frame_count().

This function is being removed from miniaudio. To make this work with
the current architecture of raylib it requires the use of a cache.

This commit implements a generic solution that works across all
AudioBuffer types (static, streams and callback based), but the static
case could be optimized to avoid the cache by incorporating the
functionality of ReadAudioBufferFramesInInternalFormat() into
ReadAudioBufferFramesInMixingFormat(). It would be unpractical to avoid
the cache with streams and callback-based AudioBuffers however so this
commit sticks with a generic solution.
pull/5568/head
David Reid 5 дней назад
Родитель
Сommit
2464227bf5
1 измененных файлов: 47 добавлений и 19 удалений
  1. +47
    -19
      src/raudio.c

+ 47
- 19
src/raudio.c Просмотреть файл

@ -295,6 +295,10 @@ typedef struct tagBITMAPINFOHEADER {
#define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Audio pool channels
#endif
#ifndef AUDIO_BUFFER_CONVERSION_CACHE_SIZE
#define AUDIO_BUFFER_CONVERSION_CACHE_SIZE 256 // In PCM frames. Smaller values use less memory but have more overhead..
#endif
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
@ -337,6 +341,9 @@ typedef enum {
// Audio buffer struct
struct rAudioBuffer {
ma_data_converter converter; // Audio data converter
unsigned char* converterCache; // Cached input samples for use by the converter when resampling is required
unsigned int converterCacheCap; // The capacity of the converter cache in frames
unsigned int converterCacheLen; // The number of valid frames sitting in the converter cache
AudioCallback callback; // Audio buffer callback for buffer filling on audio threads
rAudioProcessor *processor; // Audio processor
@ -586,6 +593,16 @@ AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sam
return NULL;
}
// A cache for use by the converter is necessary when resampling because
// when generating output frames a different number of input frames will
// be consumed. Any residual input frames need to be kept track of to
// ensure there are no discontinuities. Since raylib supports pitch
// shifting, which is done through resampling, a cache will always be
// required. This will be kept relatively small to avoid too much wastage.
audioBuffer->converterCacheLen = 0;
audioBuffer->converterCacheCap = AUDIO_BUFFER_CONVERSION_CACHE_SIZE;
audioBuffer->converterCache = (unsigned char*)RL_CALLOC(audioBuffer->converterCacheCap*ma_get_bytes_per_frame(format, channels), 1);
// Init audio buffer values
audioBuffer->volume = 1.0f;
audioBuffer->pitch = 1.0f;
@ -621,6 +638,7 @@ void UnloadAudioBuffer(AudioBuffer *buffer)
{
UntrackAudioBuffer(buffer);
ma_data_converter_uninit(&buffer->converter, NULL);
RL_FREE(buffer->converterCache);
RL_FREE(buffer->data);
RL_FREE(buffer);
}
@ -2456,11 +2474,7 @@ static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, f
// NOTE: Continuously converting data from the AudioBuffer's internal format to the mixing format,
// which should be defined by the output format of the data converter.
// This is done until frameCount frames have been output.
// The important detail to remember is that more data than required should neeveer be read,
// for the specified number of output frames.
// This can be achieved with ma_data_converter_get_required_input_frame_count()
ma_uint8 inputBuffer[4096] = { 0 };
ma_uint32 inputBufferFrameCap = sizeof(inputBuffer)/ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn);
// A cache is required to ensure continuity when resampling.
ma_uint32 totalOutputFramesProcessed = 0;
while (totalOutputFramesProcessed < frameCount)
@ -2468,26 +2482,40 @@ static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, f
ma_uint64 outputFramesToProcessThisIteration = frameCount - totalOutputFramesProcessed;
ma_uint64 inputFramesToProcessThisIteration = 0;
(void)ma_data_converter_get_required_input_frame_count(&audioBuffer->converter, outputFramesToProcessThisIteration, &inputFramesToProcessThisIteration);
if (inputFramesToProcessThisIteration > inputBufferFrameCap)
// Output frames come from the converter. The converter reads from the cache. The process
// goes like this:
//
// AudioBuffer -> Cache -> Converter -> framesOut
//
// Data is moved from the AudioBuffer into the cache, and then the cache is fed into the
// converter which outputs to the output buffer.
// Refill the cache if necessary.
if (audioBuffer->converterCacheLen == 0)
{
inputFramesToProcessThisIteration = inputBufferFrameCap;
audioBuffer->converterCacheLen = ReadAudioBufferFramesInInternalFormat(audioBuffer, audioBuffer->converterCache, audioBuffer->converterCacheCap);
}
float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.channelsOut);
// At this point we can convert the data to our mixing format
ma_uint64 inputFramesProcessedThisIteration = ReadAudioBufferFramesInInternalFormat(audioBuffer, inputBuffer, (ma_uint32)inputFramesToProcessThisIteration);
ma_uint64 outputFramesProcessedThisIteration = outputFramesToProcessThisIteration;
ma_data_converter_process_pcm_frames(&audioBuffer->converter, inputBuffer, &inputFramesProcessedThisIteration, runningFramesOut, &outputFramesProcessedThisIteration);
// Now run the data through the data converter.
if (audioBuffer->converterCacheLen > 0)
{
ma_uint32 bpf = ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn);
float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.channelsOut);
totalOutputFramesProcessed += (ma_uint32)outputFramesProcessedThisIteration; // Safe cast
ma_uint64 inputFramesProcessedThisIteration = audioBuffer->converterCacheLen;
ma_uint64 outputFramesProcessedThisIteration = outputFramesToProcessThisIteration;
ma_data_converter_process_pcm_frames(&audioBuffer->converter, audioBuffer->converterCache, &inputFramesProcessedThisIteration, runningFramesOut, &outputFramesProcessedThisIteration);
if (inputFramesProcessedThisIteration < inputFramesToProcessThisIteration) break; // Ran out of input data
// Make sure the data in the cache is consumed. This can be optimized to use a cursor instead of a memmove().
memmove(audioBuffer->converterCache, audioBuffer->converterCache + inputFramesProcessedThisIteration*bpf, (size_t)(audioBuffer->converterCacheCap - inputFramesProcessedThisIteration) * bpf);
audioBuffer->converterCacheLen -= (ma_uint32)inputFramesProcessedThisIteration; // Safe cast
// This should never be hit, but added here for safety
// Ensures we get out of the loop when no input nor output frames are processed
if ((inputFramesProcessedThisIteration == 0) && (outputFramesProcessedThisIteration == 0)) break;
totalOutputFramesProcessed += (ma_uint32)outputFramesProcessedThisIteration; // Safe cast
}
else
{
break; // Ran out of input data.
}
}
return totalOutputFramesProcessed;

Загрузка…
Отмена
Сохранить