From f0ac8123d96f5945bc74e1fb4aad3270dc986297 Mon Sep 17 00:00:00 2001 From: Bobby Smiles Date: Sun, 17 Jan 2021 23:07:33 +0100 Subject: [PATCH] Allow circular buffer to report disjoint buffer This allows to avoid memmove after each consume_cbuff_data. Some extra care was necessary to round queue transactions to a multiple of the sample size. I also did some refactoring on the cbuff to allow it to grow and on the resamplers to allw them to report not only how many bytes were consumed, but also how many bytes were produced, so we can move cbuff pointers accordingly. tail/head methods reports the available sizes in 2 parameters : "available" for the first chunk size, and "extra" for the optional second chunk (which starts at the buffer beginning). It's up to the caller to split it's contiguous accesses around these two chunks. Another method that I initially wanted to try was the virtual memory trick to mirror the beggining of the buffer at the end of itself. But this trick requires some code specific platform and can be tricky when we need to grow the buffer. So I went this explicit 2 chunk approach which is simpler and doesn't require platform specific code. --- src/circular_buffer.c | 65 ++++++++++++++++++---- src/circular_buffer.h | 7 ++- src/main.c | 16 +++--- src/main.h | 8 +-- src/resamplers/resamplers.h | 6 +-- src/resamplers/speex.c | 19 +++---- src/resamplers/src.c | 19 +++---- src/resamplers/trivial.c | 9 ++-- src/sdl_backend.c | 104 +++++++++++++++++++++++------------- 9 files changed, 163 insertions(+), 90 deletions(-) diff --git a/src/circular_buffer.c b/src/circular_buffer.c index 6e5da6b..955bbd2 100644 --- a/src/circular_buffer.c +++ b/src/circular_buffer.c @@ -48,36 +48,83 @@ void release_cbuff(struct circular_buffer* cbuff) memset(cbuff, 0, sizeof(*cbuff)); } +void grow_cbuff(struct circular_buffer* cbuff, size_t new_size) +{ + if (new_size >= cbuff->size) { + + cbuff->data = realloc(cbuff->data, new_size); + + size_t delta = new_size - cbuff->size; + size_t delta_max = (cbuff->head >= cbuff->tail) ? cbuff->tail : cbuff->head; -void* cbuff_head(const struct circular_buffer* cbuff, size_t* available) + if (delta_max <= delta) { + memcpy((unsigned char*)cbuff->data + cbuff->size, cbuff->data, delta_max); + memset((unsigned char*)cbuff->data + cbuff->size + delta_max, 0, delta - delta_max); + } + else { + memcpy((unsigned char*)cbuff->data + cbuff->size, cbuff->data, delta); + memmove(cbuff->data, (const unsigned char*)cbuff->data + delta, delta_max - delta); + } + + cbuff->size = new_size; + } +} + + +void* cbuff_head(const struct circular_buffer* cbuff, size_t* available, size_t* extra) { assert(cbuff->head <= cbuff->size); + assert(cbuff->tail <= cbuff->size); + + if (cbuff->head >= cbuff->tail) { + *available = cbuff->size - cbuff->head; + *extra = cbuff->tail; + } + else { + *available = cbuff->tail - cbuff->head; + *extra = 0; + } - *available = cbuff->size - cbuff->head; return (unsigned char*)cbuff->data + cbuff->head; } -void* cbuff_tail(const struct circular_buffer* cbuff, size_t* available) +void* cbuff_tail(const struct circular_buffer* cbuff, size_t* available, size_t* extra) { - *available = cbuff->head; - return cbuff->data; + assert(cbuff->head <= cbuff->size); + assert(cbuff->tail <= cbuff->size); + + if (cbuff->head >= cbuff->tail) { + *available = cbuff->head - cbuff->tail; + *extra = 0; + } + else { + *available = cbuff->size - cbuff->tail; + *extra = cbuff->head; + } + + return (unsigned char*)cbuff->data + cbuff->tail; } void produce_cbuff_data(struct circular_buffer* cbuff, size_t amount) { - assert(cbuff->head + amount <= cbuff->size); + //assert((cbuff->head + amount) % cbuff->size <= cbuff->tail); cbuff->head += amount; + if (cbuff->head > cbuff->size) { + cbuff->head -= cbuff->size; + } } void consume_cbuff_data(struct circular_buffer* cbuff, size_t amount) { - assert(cbuff->head >= amount); + //assert((cbuff->tail + amount) % cbuff->size <= cbuff->tail); - memmove(cbuff->data, (unsigned char*)cbuff->data + amount, cbuff->head - amount); - cbuff->head -= amount; + cbuff->tail += amount; + if (cbuff->tail > cbuff->size) { + cbuff->tail -= cbuff->size; + } } diff --git a/src/circular_buffer.h b/src/circular_buffer.h index f253bd8..61b7077 100644 --- a/src/circular_buffer.h +++ b/src/circular_buffer.h @@ -29,15 +29,18 @@ struct circular_buffer void* data; size_t size; size_t head; + size_t tail; }; int init_cbuff(struct circular_buffer* cbuff, size_t capacity); void release_cbuff(struct circular_buffer* cbuff); -void* cbuff_head(const struct circular_buffer* cbuff, size_t* available); +void grow_cbuff(struct circular_buffer* cbuff, size_t new_size); -void* cbuff_tail(const struct circular_buffer* cbuff, size_t* available); +void* cbuff_head(const struct circular_buffer* cbuff, size_t* available, size_t* extra); + +void* cbuff_tail(const struct circular_buffer* cbuff, size_t* available, size_t* extra); void produce_cbuff_data(struct circular_buffer* cbuff, size_t amount); diff --git a/src/main.c b/src/main.c index 546bddd..9677ea7 100644 --- a/src/main.c +++ b/src/main.c @@ -381,27 +381,23 @@ EXPORT void CALL SetSpeedFactor(int percentage) sdl_set_speed_factor(l_sdl_backend, percentage); } -size_t ResampleAndMix(void* resampler, const struct resampler_interface* iresampler, - void* mix_buffer, - const void* src, size_t src_size, unsigned int src_freq, - void* dst, size_t dst_size, unsigned int dst_freq) +void ResampleAndMix(void* resampler, const struct resampler_interface* iresampler, + void* mix_buffer, + const void* src, size_t src_size, unsigned int src_freq, size_t* consumed, + void* dst, size_t dst_size, unsigned int dst_freq, size_t* produced) { - size_t consumed; - #if defined(HAS_OSS_SUPPORT) if (VolumeControlType == VOLUME_TYPE_OSS) { - consumed = iresampler->resample(resampler, src, src_size, src_freq, dst, dst_size, dst_freq); + iresampler->resample(resampler, src, src_size, src_freq, consumed, dst, dst_size, dst_freq, produced); } else #endif { - consumed = iresampler->resample(resampler, src, src_size, src_freq, mix_buffer, dst_size, dst_freq); + iresampler->resample(resampler, src, src_size, src_freq, consumed, mix_buffer, dst_size, dst_freq, produced); memset(dst, 0, dst_size); SDL_MixAudio(dst, mix_buffer, dst_size, VolSDL); } - - return consumed; } void SetPlaybackVolume(void) diff --git a/src/main.h b/src/main.h index 5f8fc86..61ff92a 100644 --- a/src/main.h +++ b/src/main.h @@ -40,10 +40,10 @@ enum { void SetPlaybackVolume(void); -size_t ResampleAndMix(void* resampler, const struct resampler_interface* iresampler, - void* mix_buffer, - const void* src, size_t src_size, unsigned int src_freq, - void* dst, size_t dst_size, unsigned int dst_freq); +void ResampleAndMix(void* resampler, const struct resampler_interface* iresampler, + void* mix_buffer, + const void* src, size_t src_size, unsigned int src_freq, size_t* consumed, + void* dst, size_t dst_size, unsigned int dst_freq, size_t* produced); /* declarations of pointers to Core config functions */ extern ptr_ConfigListSections ConfigListSections; diff --git a/src/resamplers/resamplers.h b/src/resamplers/resamplers.h index d3a398f..b64b21c 100644 --- a/src/resamplers/resamplers.h +++ b/src/resamplers/resamplers.h @@ -32,9 +32,9 @@ struct resampler_interface void (*release)(void* resampler); - size_t (*resample)(void* resampler, - const void* src, size_t src_size, unsigned int src_freq, - void* dst, size_t dst_size, unsigned int dst_freq); + void (*resample)(void* resampler, + const void* src, size_t src_size, unsigned int src_freq, size_t* consumed, + void* dst, size_t dst_size, unsigned int dst_freq, size_t* produced); }; const struct resampler_interface* get_iresampler(const char* resampler_id, void** resampler); diff --git a/src/resamplers/speex.c b/src/resamplers/speex.c index 66fce04..c0b14c2 100644 --- a/src/resamplers/speex.c +++ b/src/resamplers/speex.c @@ -93,9 +93,9 @@ static void speex_release(void* resampler) speex_resampler_destroy(spx_state); } -static size_t speex_resample(void* resampler, - const void* src, size_t src_size, unsigned int src_freq, - void* dst, size_t dst_size, unsigned int dst_freq) +static void speex_resample(void* resampler, + const void* src, size_t src_size, unsigned int src_freq, size_t* consumed, + void* dst, size_t dst_size, unsigned int dst_freq, size_t* produced) { SpeexResamplerState* spx_state = (SpeexResamplerState*)resampler; @@ -111,17 +111,14 @@ static size_t speex_resample(void* resampler, if (error != RESAMPLER_ERR_SUCCESS) { DebugMessage(M64MSG_ERROR, "Speex error: %s", speex_resampler_strerror(error)); + *consumed = src_size; + *produced = dst_size; memset(dst, 0, dst_size); - return src_size; - } - - if (dst_size != out_len * BYTES_PER_SAMPLE) { - DebugMessage(M64MSG_WARNING, "dst_size = %u != outlen*4 = %u", - (uint32_t) dst_size, out_len * BYTES_PER_SAMPLE); + return; } - memset((char*)dst + out_len * BYTES_PER_SAMPLE, 0, dst_size - out_len * BYTES_PER_SAMPLE); - return in_len * BYTES_PER_SAMPLE; + *consumed = in_len * BYTES_PER_SAMPLE; + *produced = out_len * BYTES_PER_SAMPLE; } diff --git a/src/resamplers/src.c b/src/resamplers/src.c index 29ec078..462ac33 100644 --- a/src/resamplers/src.c +++ b/src/resamplers/src.c @@ -133,9 +133,9 @@ static void src_release(void* resampler) } } -static size_t src_resample(void* resampler, - const void* src, size_t src_size, unsigned int src_freq, - void* dst, size_t dst_size, unsigned int dst_freq) +static void src_resample(void* resampler, + const void* src, size_t src_size, unsigned int src_freq, size_t* consumed, + void* dst, size_t dst_size, unsigned int dst_freq, size_t* produced) { struct src_resampler* src_resampler = (struct src_resampler*)resampler; @@ -176,19 +176,16 @@ static size_t src_resample(void* resampler, if (error) { DebugMessage(M64MSG_ERROR, "SRC error: %s", src_strerror(error)); + *consumed = src_size; + *produced = dst_size; memset(dst, 0, dst_size); - return src_size; - } - - if (dst_size != src_data.output_frames_gen*4) { - DebugMessage(M64MSG_WARNING, "dst_size = %u != output_frames_gen*4 = %u", - (uint32_t) dst_size, (uint32_t) src_data.output_frames_gen*4); + return; } src_float_to_short_array(src_resampler->fbuffers[1].data, (short*)dst, src_data.output_frames_gen*2); - memset((char*)dst + src_data.output_frames_gen*4, 0, dst_size - src_data.output_frames_gen*4); - return src_data.input_frames_used * 4; + *consumed = src_data.input_frames_used * 4; + *produced = src_data.output_frames_gen * 4; } diff --git a/src/resamplers/trivial.c b/src/resamplers/trivial.c index 863ba8e..f8d4b60 100644 --- a/src/resamplers/trivial.c +++ b/src/resamplers/trivial.c @@ -35,9 +35,9 @@ static void trivial_release(void* resampler) /* nothing to do */ } -static size_t trivial_resample(void* resampler, - const void* src, size_t src_size, unsigned int src_freq, - void* dst, size_t dst_size, unsigned int dst_freq) +static void trivial_resample(void* resampler, + const void* src, size_t src_size, unsigned int src_freq, size_t* consumed, + void* dst, size_t dst_size, unsigned int dst_freq, size_t* produced) { enum { BYTES_PER_SAMPLE = 4 }; size_t i; @@ -71,7 +71,8 @@ static size_t trivial_resample(void* resampler, } } - return j * 4; + *consumed = j * 4; + *produced = i * 4; } diff --git a/src/sdl_backend.c b/src/sdl_backend.c index 71aecda..9249fc8 100644 --- a/src/sdl_backend.c +++ b/src/sdl_backend.c @@ -23,6 +23,7 @@ #include #include #include +#include #include "circular_buffer.h" #include "main.h" @@ -118,43 +119,50 @@ static void my_audio_callback(void* userdata, unsigned char* stream, int len) unsigned int newsamplerate = sdl_backend->output_frequency * 100 / sdl_backend->speed_factor; unsigned int oldsamplerate = sdl_backend->input_frequency; - size_t needed = (len * oldsamplerate) / newsamplerate; - size_t available; - size_t consumed; + size_t available, extra; + size_t consumed = 0; + size_t produced = 0; + size_t consumed2 = 0; + size_t produced2 = 0; - const void* src = cbuff_tail(&sdl_backend->primary_buffer, &available); - if ((available > 0) && (available >= needed)) - { - consumed = ResampleAndMix(sdl_backend->resampler, sdl_backend->iresampler, + const void* src = cbuff_tail(&sdl_backend->primary_buffer, &available, &extra); + + assert((len & 0x3) == 0 && (available & 0x3) == 0 && (extra & 0x3) == 0); + + if ((available + extra) > 0) { + ResampleAndMix(sdl_backend->resampler, sdl_backend->iresampler, + sdl_backend->mix_buffer, + src, available, oldsamplerate, &consumed, + stream, len, newsamplerate, &produced); + + if (produced < len) { + ResampleAndMix(sdl_backend->resampler, sdl_backend->iresampler, sdl_backend->mix_buffer, - src, available, oldsamplerate, - stream, len, newsamplerate); + (unsigned char*)sdl_backend->primary_buffer.data, extra, oldsamplerate, &consumed2, + stream + produced, len - produced, newsamplerate, &produced2); + } - consume_cbuff_data(&sdl_backend->primary_buffer, consumed); + assert((consumed & 0x3) == 0 && (consumed2 & 0x3) == 0 && (produced & 0x3) == 0 && (produced2 & 0x3) == 0); + consume_cbuff_data(&sdl_backend->primary_buffer, consumed + consumed2); } - else - { + + if (produced + produced2 < len) { ++sdl_backend->underrun_count; - memset(stream, 0, len); + memset(stream + produced + produced2, 0, len - (produced + produced2)); } } static size_t new_primary_buffer_size(const struct sdl_backend* sdl_backend) { - return N64_SAMPLE_BYTES * ((uint64_t)sdl_backend->primary_buffer_size * sdl_backend->input_frequency * sdl_backend->speed_factor) / - (sdl_backend->output_frequency * 100); + return N64_SAMPLE_BYTES * (((uint64_t)sdl_backend->primary_buffer_size * sdl_backend->input_frequency * sdl_backend->speed_factor) / + (sdl_backend->output_frequency * 100)); } static void resize_primary_buffer(struct sdl_backend* sdl_backend, size_t new_size) { - /* only grows the buffer */ - if (new_size > sdl_backend->primary_buffer.size) { - SDL_LockAudio(); - sdl_backend->primary_buffer.data = realloc(sdl_backend->primary_buffer.data, new_size); - memset((unsigned char*)sdl_backend->primary_buffer.data + sdl_backend->primary_buffer.size, 0, new_size - sdl_backend->primary_buffer.size); - sdl_backend->primary_buffer.size = new_size; - SDL_UnlockAudio(); - } + SDL_LockAudio(); + grow_cbuff(&sdl_backend->primary_buffer, new_size); + SDL_UnlockAudio(); } static unsigned int select_output_frequency(unsigned int input_frequency) @@ -359,7 +367,7 @@ void sdl_set_frequency(struct sdl_backend* sdl_backend, unsigned int frequency) void sdl_push_samples(struct sdl_backend* sdl_backend, const void* src, size_t size) { - size_t available; + size_t available, extra; if (sdl_backend->error != 0) return; @@ -372,8 +380,11 @@ void sdl_push_samples(struct sdl_backend* sdl_backend, const void* src, size_t s /* We need to lock audio before accessing cbuff */ SDL_LockAudio(); - unsigned char* dst = cbuff_head(&sdl_backend->primary_buffer, &available); - if (size <= available) + unsigned char* dst = cbuff_head(&sdl_backend->primary_buffer, &available, &extra); + + assert((size & 0x3) == 0 && (available & 0x3) == 0 && (extra & 0x3) == 0); + + if (size <= (available+extra)) { /* Confusing logic but, for LittleEndian host using memcpy will result in swapped channels, * whereas the other branch will result in non-swapped channels. @@ -387,14 +398,35 @@ void sdl_push_samples(struct sdl_backend* sdl_backend, const void* src, size_t s * memcpy path results in the non-swapped channels outcome. */ if (sdl_backend->swap_channels ^ (SDL_BYTEORDER == SDL_BIG_ENDIAN)) { - memcpy(dst, src, size); + if (size <= available) { + memcpy(dst, src, size); + } + else { + memcpy(dst, src, available); + memcpy(sdl_backend->primary_buffer.data, (const unsigned char*)src + available, size - available); + } } else { size_t i; - for (i = 0 ; i < size ; i += 4 ) - { - memcpy(dst + i + 0, (const unsigned char*)src + i + 2, 2); /* Left */ - memcpy(dst + i + 2, (const unsigned char*)src + i + 0, 2); /* Right */ + + if (size <= available) { + for (i = 0 ; i < size ; i += 4) + { + memcpy(dst + i + 0, (const unsigned char*)src + i + 2, 2); /* Left */ + memcpy(dst + i + 2, (const unsigned char*)src + i + 0, 2); /* Right */ + } + } + else { + for (i = 0 ; i < available ; i += 4) + { + memcpy(dst + i + 0, (const unsigned char*)src + i + 2, 2); /* Left */ + memcpy(dst + i + 2, (const unsigned char*)src + i + 0, 2); /* Right */ + } + for (i = 0; i < (size - available); i += 4) + { + memcpy((unsigned char*)sdl_backend->primary_buffer.data + i + 0, (const unsigned char*)src + available + i + 2, 2); /* Left */ + memcpy((unsigned char*)sdl_backend->primary_buffer.data + i + 2, (const unsigned char*)src + available + i + 0, 2); /* Right */ + } } } @@ -402,23 +434,23 @@ void sdl_push_samples(struct sdl_backend* sdl_backend, const void* src, size_t s } SDL_UnlockAudio(); - if (size > available) + if (size > (available+extra)) { - DebugMessage(M64MSG_WARNING, "sdl_push_samples: pushing %zu bytes, but only %zu available !", size, available); + DebugMessage(M64MSG_WARNING, "sdl_push_samples: pushing %zu bytes, but only %zu available !", size, available+extra); } } static size_t estimate_level_at_next_audio_cb(struct sdl_backend* sdl_backend) { - size_t available; + size_t available, extra; unsigned int now = SDL_GetTicks(); /* NOTE: given that we only access "available" counter from cbuff, we don't need to protect it's access with LockAudio/UnlockAudio */ - cbuff_tail(&sdl_backend->primary_buffer, &available); + cbuff_tail(&sdl_backend->primary_buffer, &available, &extra); /* Start by calculating the current Primary buffer fullness in terms of output samples */ - size_t expected_level = (size_t)(((int64_t)(available/N64_SAMPLE_BYTES) * sdl_backend->output_frequency * 100) / (sdl_backend->input_frequency * sdl_backend->speed_factor)); + size_t expected_level = (size_t)(((int64_t)((available+extra)/N64_SAMPLE_BYTES) * sdl_backend->output_frequency * 100) / (sdl_backend->input_frequency * sdl_backend->speed_factor)); /* Next, extrapolate to the buffer level at the expected time of the next audio callback, assuming that the buffer is filled at the same rate as the output frequency */