Files
RMG/Source/RMG-Audio/sdl_backend.cpp

279 lines
9.2 KiB
C++

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 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 <SDL3/SDL.h>
#include <SDL3/SDL_audio.h>
#include <stdlib.h>
#include <string.h>
#include "Resamplers/resamplers.hpp"
#include "main.hpp"
#include <RMG-Core/m64p/api/m64p_types.h>
#include <RMG-Core/Settings.hpp>
#include <RMG-Core/Netplay.hpp>
/* number of bytes per sample */
#define N64_SAMPLE_BYTES 4
#define SDL_SAMPLE_BYTES 4
struct sdl_backend
{
/* Audio Stream */
SDL_AudioStream* stream;
/* Primary Buffer */
void* primary_buffer;
/* Resampling buffer */
void* resample_buffer;
/* Size of buffers */
size_t buffers_size;
unsigned int frequency;
unsigned int speed_factor;
unsigned int swap_channels;
unsigned int error;
float volume;
/* 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 sdl_init_audio_device(struct sdl_backend* sdl_backend)
{
SDL_AudioSpec spec;
sdl_backend->error = 0;
if (SDL_WasInit(SDL_INIT_AUDIO))
{
DebugMessage(M64MSG_VERBOSE, "sdl_init_audio_device(): SDL Audio sub-system already initialized.");
SDL_DestroyAudioStream(sdl_backend->stream);
}
else
{
if (!SDL_Init(SDL_INIT_AUDIO))
{
DebugMessage(M64MSG_ERROR, "Failed to initialize SDL audio subsystem.");
sdl_backend->error = 1;
return;
}
}
DebugMessage(M64MSG_INFO, "Initializing SDL audio subsystem...");
memset(&spec, 0, sizeof(spec));
spec.freq = sdl_backend->frequency;
spec.format = SDL_AUDIO_S16LE;
spec.channels = 2;
/* Open the audio device */
sdl_backend->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL);
if (sdl_backend->stream == NULL)
{
DebugMessage(M64MSG_ERROR, "Couldn't open audio stream: %s", SDL_GetError());
sdl_backend->error = 1;
return;
}
DebugMessage(M64MSG_VERBOSE, "Frequency: %i", spec.freq);
DebugMessage(M64MSG_VERBOSE, "Format: " AFMT_FMTSPEC, AFMT_ARGS(spec.format));
DebugMessage(M64MSG_VERBOSE, "Channels: %i", spec.channels);
/* start audio stream */
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(sdl_backend->stream));
}
static void release_audio_device(struct sdl_backend* sdl_backend)
{
if (SDL_WasInit(SDL_INIT_AUDIO)) {
SDL_DestroyAudioStream(sdl_backend->stream);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
}
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->frequency = CoreSettingsGetIntValue(SettingsID::Audio_DefaultFrequency);
sdl_backend->swap_channels = CoreSettingsGetBoolValue(SettingsID::Audio_SwapChannels);
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->frequency = CoreSettingsGetIntValue(SettingsID::Audio_DefaultFrequency);
sdl_backend->swap_channels = CoreSettingsGetBoolValue(SettingsID::Audio_SwapChannels);
}
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 */
if (sdl_backend->primary_buffer != nullptr) {
free(sdl_backend->primary_buffer);
}
/* release mix buffer */
if (sdl_backend->resample_buffer != nullptr) {
free(sdl_backend->resample_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->frequency = frequency;
sdl_init_audio_device(sdl_backend);
}
void sdl_push_samples(struct sdl_backend* sdl_backend, const void* src, size_t size)
{
if (sdl_backend->error != 0)
return;
// taken from https://github.com/gopher64/gopher64/blob/f3271cba63571d4d42c84c4c2db891af125b6f8d/src/ui/audio.rs
double audioQueued = (double)SDL_GetAudioStreamQueued(sdl_backend->stream);
double acceptableLatency = ((double)sdl_backend->frequency * 0.2) * 4.0;
if (audioQueued >= acceptableLatency)
{
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;
/* resize buffers when required */
if (size > sdl_backend->buffers_size)
{
sdl_backend->primary_buffer = realloc(sdl_backend->primary_buffer, size);
sdl_backend->resample_buffer = realloc(sdl_backend->resample_buffer, size);
sdl_backend->buffers_size = size;
}
/* 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(sdl_backend->primary_buffer, src, size);
}
else {
size_t i;
for (i = 0 ; i < size ; i += 4 )
{
memcpy((unsigned char*)sdl_backend->primary_buffer + i + 0, (const unsigned char*)src + i + 2, 2); /* Left */
memcpy((unsigned char*)sdl_backend->primary_buffer + i + 2, (const unsigned char*)src + i + 0, 2); /* Right */
}
}
/* resample audio */
sdl_backend->iresampler->resample(sdl_backend->resampler,
sdl_backend->primary_buffer, size,
sdl_backend->frequency,
sdl_backend->resample_buffer, size,
sdl_backend->frequency);
/* push audio buffer to SDL */
SDL_PutAudioStreamData(sdl_backend->stream, sdl_backend->resample_buffer, size);
}
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;
}
void sdl_apply_volume(struct sdl_backend* sdl_backend, float vol)
{
if (sdl_backend->stream != nullptr)
{
SDL_SetAudioStreamGain(sdl_backend->stream, vol);
}
}