/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Mupen64plus-sdl-audio - sdl_backend.c * * Mupen64Plus homepage: https://mupen64plus.org/ * * Copyright (C) 2017 Bobby Smiles * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include #include #include #include #include "Resamplers/resamplers.hpp" #include "circular_buffer.hpp" #include "main.hpp" #include #include #include /* number of bytes per sample */ #define N64_SAMPLE_BYTES 4 #define SDL_SAMPLE_BYTES 4 #define SDL_LockAudio() SDL_LockAudioDevice(sdl_backend->device) #define SDL_UnlockAudio() SDL_UnlockAudioDevice(sdl_backend->device) #define SDL_PauseAudio(A) SDL_PauseAudioDevice(sdl_backend->device, A) #define SDL_CloseAudio() SDL_CloseAudioDevice(sdl_backend->device) #define SDL_OpenAudio(A, B) ((sdl_backend->device = SDL_OpenAudioDevice(nullptr, 0, A, B, 0)) - 1) struct sdl_backend { /* Audio Device */ SDL_AudioDeviceID device; /* Primary Buffer */ struct circular_buffer primary_buffer; /* Primary buffer size (in output samples) */ size_t primary_buffer_size; /* Primary buffer fullness target (in output samples) */ size_t target; /* Secondary buffer size (in output samples) */ size_t secondary_buffer_size; /* Mixing buffer used for volume control */ unsigned char* mix_buffer; unsigned int last_cb_time; unsigned int input_frequency; unsigned int output_frequency; unsigned int speed_factor; unsigned int swap_channels; unsigned int audio_sync; unsigned int paused_for_sync; unsigned int underrun_count; unsigned int error; /* Resampler */ void* resampler; const struct resampler_interface* iresampler; }; /* SDL_AudioFormat.format format specifier and args builder */ #define AFMT_FMTSPEC "%c%d%s" #define AFMT_ARGS(x) \ ((SDL_AUDIO_ISFLOAT(x)) ? 'F' : (SDL_AUDIO_ISSIGNED(x)) ? 'S' : 'U'), \ SDL_AUDIO_BITSIZE(x), \ SDL_AUDIO_ISBIGENDIAN(x) ? "BE" : "LE" static void my_audio_callback(void* userdata, unsigned char* stream, int len) { struct sdl_backend* sdl_backend = (struct sdl_backend*)userdata; /* mark the time, for synchronization on the input side */ sdl_backend->last_cb_time = SDL_GetTicks(); 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; const void* src = cbuff_tail(&sdl_backend->primary_buffer, &available); if ((available > 0) && (available >= needed)) { consumed = ResampleAndMix(sdl_backend->resampler, sdl_backend->iresampler, sdl_backend->mix_buffer, src, available, oldsamplerate, stream, len, newsamplerate); consume_cbuff_data(&sdl_backend->primary_buffer, consumed); } else { ++sdl_backend->underrun_count; memset(stream, 0, len); } } 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); } 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(); } } static unsigned int select_output_frequency(unsigned int input_frequency) { if (input_frequency <= 11025) { return 11025; } else if (input_frequency <= 22050) { return 22050; } else { return 44100; } } static void sdl_init_audio_device(struct sdl_backend* sdl_backend) { SDL_AudioSpec desired, obtained; sdl_backend->error = 0; if (SDL_WasInit(SDL_INIT_AUDIO|SDL_INIT_TIMER) == (SDL_INIT_AUDIO|SDL_INIT_TIMER) ) { DebugMessage(M64MSG_VERBOSE, "sdl_init_audio_device(): SDL Audio sub-system already initialized."); SDL_PauseAudio(1); SDL_CloseAudio(); } else { if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) { DebugMessage(M64MSG_ERROR, "Failed to initialize SDL audio subsystem."); sdl_backend->error = 1; return; } } sdl_backend->paused_for_sync = 1; /* reload these because they gets re-assigned from SDL data below, and sdl_init_audio_device can be called more than once */ sdl_backend->primary_buffer_size = CoreSettingsGetIntValue(SettingsID::Audio_PrimaryBufferSize); sdl_backend->target = CoreSettingsGetIntValue(SettingsID::Audio_PrimaryBufferTarget); sdl_backend->secondary_buffer_size = CoreSettingsGetIntValue(SettingsID::Audio_SecondaryBufferSize); DebugMessage(M64MSG_INFO, "Initializing SDL audio subsystem..."); DebugMessage(M64MSG_VERBOSE, "Primary buffer: %i output samples.", (uint32_t) sdl_backend->primary_buffer_size); DebugMessage(M64MSG_VERBOSE, "Primary target fullness: %i output samples.", (uint32_t) sdl_backend->target); DebugMessage(M64MSG_VERBOSE, "Secondary buffer: %i output samples.", (uint32_t) sdl_backend->secondary_buffer_size); memset(&desired, 0, sizeof(desired)); desired.freq = select_output_frequency(sdl_backend->input_frequency); desired.format = AUDIO_S16SYS; desired.channels = 2; desired.samples = sdl_backend->secondary_buffer_size; desired.callback = my_audio_callback; desired.userdata = sdl_backend; DebugMessage(M64MSG_VERBOSE, "Requesting frequency: %iHz.", desired.freq); DebugMessage(M64MSG_VERBOSE, "Requesting format: " AFMT_FMTSPEC ".", AFMT_ARGS(desired.format)); /* Open the audio device */ if (SDL_OpenAudio(&desired, &obtained) < 0) { DebugMessage(M64MSG_ERROR, "Couldn't open audio: %s", SDL_GetError()); sdl_backend->error = 1; return; } if (desired.format != obtained.format) { DebugMessage(M64MSG_WARNING, "Obtained audio format (" AFMT_FMTSPEC ") differs from requested (" AFMT_FMTSPEC ").", AFMT_ARGS(obtained.format), AFMT_ARGS(desired.format)); } if (desired.freq != obtained.freq) { DebugMessage(M64MSG_WARNING, "Obtained frequency (%i) differs from requested (%i).", obtained.freq, desired.freq); } /* adjust some variables given the obtained audio spec */ sdl_backend->output_frequency = obtained.freq; sdl_backend->secondary_buffer_size = obtained.samples; if (sdl_backend->target < sdl_backend->secondary_buffer_size) sdl_backend->target = sdl_backend->secondary_buffer_size; if (sdl_backend->primary_buffer_size < sdl_backend->target) sdl_backend->primary_buffer_size = sdl_backend->target; if (sdl_backend->primary_buffer_size < sdl_backend->secondary_buffer_size * 2) sdl_backend->primary_buffer_size = sdl_backend->secondary_buffer_size * 2; /* allocate memory for audio buffers */ resize_primary_buffer(sdl_backend, new_primary_buffer_size(sdl_backend)); sdl_backend->mix_buffer = (unsigned char*)realloc(sdl_backend->mix_buffer, sdl_backend->secondary_buffer_size * SDL_SAMPLE_BYTES); /* preset the last callback time */ if (sdl_backend->last_cb_time == 0) { sdl_backend->last_cb_time = SDL_GetTicks(); } DebugMessage(M64MSG_VERBOSE, "Frequency: %i", obtained.freq); DebugMessage(M64MSG_VERBOSE, "Format: " AFMT_FMTSPEC, AFMT_ARGS(obtained.format)); DebugMessage(M64MSG_VERBOSE, "Channels: %i", obtained.channels); DebugMessage(M64MSG_VERBOSE, "Silence: %i", obtained.silence); DebugMessage(M64MSG_VERBOSE, "Samples: %i", obtained.samples); DebugMessage(M64MSG_VERBOSE, "Size: %i", obtained.size); } static void release_audio_device(struct sdl_backend* sdl_backend) { if (SDL_WasInit(SDL_INIT_AUDIO) != 0) { SDL_PauseAudio(1); SDL_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); } if (SDL_WasInit(SDL_INIT_TIMER) != 0) { SDL_QuitSubSystem(SDL_INIT_TIMER); } } struct sdl_backend* init_sdl_backend(void) { /* allocate memory for sdl_backend */ struct sdl_backend* sdl_backend = (struct sdl_backend*)malloc(sizeof(*sdl_backend)); if (sdl_backend == nullptr) { return nullptr; } /* reset sdl_backend */ memset(sdl_backend, 0, sizeof(*sdl_backend)); /* instanciate resampler */ std::string resampler_id = CoreSettingsGetStringValue(SettingsID::Audio_Resampler); void* resampler = nullptr; const struct resampler_interface* iresampler = get_iresampler(resampler_id.c_str(), &resampler); if (iresampler == nullptr) { free(sdl_backend); return nullptr; } sdl_backend->input_frequency = CoreSettingsGetIntValue(SettingsID::Audio_DefaultFrequency); sdl_backend->swap_channels = CoreSettingsGetBoolValue(SettingsID::Audio_SwapChannels); sdl_backend->audio_sync = !CoreHasInitNetplay() && CoreSettingsGetBoolValue(SettingsID::Audio_Synchronize); sdl_backend->paused_for_sync = 1; sdl_backend->speed_factor = 100; sdl_backend->resampler = resampler; sdl_backend->iresampler = iresampler; sdl_init_audio_device(sdl_backend); return sdl_backend; } void sdl_apply_settings(struct sdl_backend* sdl_backend) { sdl_backend->input_frequency = CoreSettingsGetIntValue(SettingsID::Audio_DefaultFrequency); sdl_backend->swap_channels = CoreSettingsGetBoolValue(SettingsID::Audio_SwapChannels); sdl_backend->audio_sync = CoreSettingsGetBoolValue(SettingsID::Audio_Synchronize); sdl_backend->primary_buffer_size = CoreSettingsGetIntValue(SettingsID::Audio_PrimaryBufferSize); sdl_backend->target = CoreSettingsGetIntValue(SettingsID::Audio_PrimaryBufferTarget); sdl_backend->secondary_buffer_size = CoreSettingsGetIntValue(SettingsID::Audio_SecondaryBufferSize); } void release_sdl_backend(struct sdl_backend* sdl_backend) { if (sdl_backend == nullptr) { return; } if (sdl_backend->error == 0) { release_audio_device(sdl_backend); } /* release primary buffer */ release_cbuff(&sdl_backend->primary_buffer); /* release mix buffer */ free(sdl_backend->mix_buffer); /* release resampler */ sdl_backend->iresampler->release(sdl_backend->resampler); /* release sdl backend */ free(sdl_backend); } void sdl_set_frequency(struct sdl_backend* sdl_backend, unsigned int frequency) { if (sdl_backend->error != 0) return; sdl_backend->input_frequency = frequency; sdl_init_audio_device(sdl_backend); } void sdl_push_samples(struct sdl_backend* sdl_backend, const void* src, size_t size) { size_t available; if (sdl_backend->error != 0) return; /* truncate to full samples */ if (size & 0x3) { DebugMessage(M64MSG_VERBOSE, "sdl_push_samples: pushing non full samples: %zu bytes !", size); } size = (size / 4) * 4; /* We need to lock audio before accessing cbuff */ SDL_LockAudio(); unsigned char* dst = (unsigned char*)cbuff_head(&sdl_backend->primary_buffer, &available); if (size <= available) { /* Confusing logic but, for LittleEndian host using memcpy will result in swapped channels, * whereas the other branch will result in non-swapped channels. * For BigEndian host this logic is inverted, memcpy will result in non swapped channels * and the other branch will result in swapped channels. * * This is due to the fact that the core stores 32bit words in native order in RDRAM. * For instance N64 bytes "Lh Ll Rh Rl" will be stored as "Rl Rh Ll Lh" on LittleEndian host * and therefore should the non-memcpy path to get non swapped channels, * whereas on BigEndian host the bytes will be stored as "Lh Ll Rh Rl" and therefore * memcpy path results in the non-swapped channels outcome. */ if (sdl_backend->swap_channels ^ (SDL_BYTEORDER == SDL_BIG_ENDIAN)) { memcpy(dst, src, size); } 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 */ } } produce_cbuff_data(&sdl_backend->primary_buffer, size); } SDL_UnlockAudio(); if (size > available) { DebugMessage(M64MSG_VERBOSE, "sdl_push_samples: pushing %zu bytes, but only %zu available !", size, available); } } static size_t estimate_level_at_next_audio_cb(struct sdl_backend* sdl_backend) { size_t available; 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); /* 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)); /* 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 */ unsigned int expected_next_cb_time = sdl_backend->last_cb_time + ((1000 * sdl_backend->secondary_buffer_size) / sdl_backend->output_frequency); if (now < expected_next_cb_time) { expected_level += (expected_next_cb_time - now) * sdl_backend->output_frequency / 1000; } return expected_level; } void sdl_synchronize_audio(struct sdl_backend* sdl_backend) { enum { TOLERANCE_MS = 10 }; size_t expected_level = estimate_level_at_next_audio_cb(sdl_backend); /* If the expected value of the Primary Buffer Fullness at the time of the next audio callback is more than 10 milliseconds ahead of our target buffer fullness level, then insert a delay now */ if (sdl_backend->audio_sync && expected_level >= sdl_backend->target + sdl_backend->output_frequency * TOLERANCE_MS / 1000) { /* Core is ahead of SDL audio thread, * delay emulation to allow the SDL audio thread to catch up */ unsigned int wait_time = (expected_level - sdl_backend->target) * 1000 / sdl_backend->output_frequency; if (sdl_backend->paused_for_sync) { SDL_PauseAudio(0); } sdl_backend->paused_for_sync = 0; SDL_Delay(wait_time); } else if (expected_level < sdl_backend->secondary_buffer_size) { /* Core is behind SDL audio thread (predicting an underflow), * pause the audio to let the Core catch up */ if (!sdl_backend->paused_for_sync) { SDL_PauseAudio(1); } sdl_backend->paused_for_sync = 1; } else { /* Expected fullness is within tolerance, * audio thread is running */ if (sdl_backend->paused_for_sync) { SDL_PauseAudio(0); } sdl_backend->paused_for_sync = 0; } } void sdl_set_speed_factor(struct sdl_backend* sdl_backend, unsigned int speed_factor) { if (speed_factor < 10 || speed_factor > 300) return; sdl_backend->speed_factor = speed_factor; /* we need a different size primary buffer to store the N64 samples when the speed changes */ resize_primary_buffer(sdl_backend, new_primary_buffer_size(sdl_backend)); }