Switch resampler to libsamplerate, sync to audio

This commit is contained in:
Dillon Beliveau 2022-07-24 16:36:24 -07:00
parent dd3c70e395
commit ec2ed753ba
7 changed files with 158 additions and 64 deletions

3
.gitmodules vendored
View file

@ -7,3 +7,6 @@
[submodule "src/contrib/nativefiledialog-extended"]
path = src/contrib/nativefiledialog-extended
url = https://github.com/btzy/nativefiledialog-extended.git
[submodule "src/contrib/libsamplerate"]
path = src/contrib/libsamplerate
url = https://github.com/libsndfile/libsamplerate

View file

@ -12,6 +12,7 @@ add_subdirectory(debugger)
add_subdirectory(contrib/parallel-rdp)
add_subdirectory(contrib/imgui)
add_subdirectory(contrib/cic_nus_6105)
add_subdirectory(contrib/libsamplerate)
SET(NFD_PORTAL ON CACHE BOOL "Use dbus for native file dialog instead of gtk")
add_subdirectory(contrib/nativefiledialog-extended)
add_subdirectory(tools)
@ -49,7 +50,10 @@ add_library(core
frontend/gamepad.c frontend/gamepad.h
frontend/game_db.c frontend/game_db.h)
target_link_libraries(core r4300i rsp rdp parallel-rdp debugger ${SDL2_LIBRARY} cic_nus_6105)
target_include_directories(core PUBLIC
contrib/libsamplerate/include)
target_link_libraries(core r4300i rsp rdp parallel-rdp debugger ${SDL2_LIBRARY} cic_nus_6105 samplerate)
add_library(imgui-ui
imgui/imgui_ui.cpp imgui/imgui_ui.h)

View file

@ -1,5 +1,6 @@
add_library(common
log.c log.h
metrics.c metrics.h
util.h)
util.h
ring_buffer.h)

59
src/common/ring_buffer.h Normal file
View file

@ -0,0 +1,59 @@
#ifndef N64_RING_BUFFER_H
#define N64_RING_BUFFER_H
#include <stdatomic.h>
#include <util.h>
#include <SDL.h>
typedef struct ring_buffer {
int capacity;
volatile unsigned size;
volatile unsigned write_idx;
volatile unsigned read_idx;
volatile bool full;
float* data;
SDL_mutex* mutex;
} ring_buffer_t;
INLINE void ring_buffer_init(ring_buffer_t* buf, uint capacity) {
buf->capacity = capacity;
buf->data = malloc(capacity * sizeof(float));
buf->size = 0;
buf->full = false;
buf->write_idx = 0;
buf->read_idx = 0;
buf->mutex = SDL_CreateMutex();
}
INLINE void ring_buffer_push_blocking(ring_buffer_t* buf, float val) {
while (buf->full) {}
SDL_LockMutex(buf->mutex);
buf->data[buf->write_idx] = val;
buf->write_idx++;
if (buf->write_idx >= buf->capacity) {
buf->write_idx = 0;
}
buf->size++;
buf->full = buf->size == buf->capacity;
SDL_UnlockMutex(buf->mutex);
}
INLINE float ring_buffer_pop(ring_buffer_t* buf) {
if (buf->size == 0) {
return 0;
} else {
SDL_LockMutex(buf->mutex);
float val = buf->data[buf->read_idx];
buf->read_idx++;
if (buf->read_idx >= buf->capacity) {
buf->read_idx = 0;
}
buf->size--;
buf->full = buf->size == buf->capacity;
SDL_UnlockMutex(buf->mutex);
return val;
}
}
#endif //N64_RING_BUFFER_H

@ -0,0 +1 @@
Subproject commit aee38d0bc797d0d1a3774ef574af1d5d248d2398

View file

@ -1,67 +1,71 @@
#include "audio.h"
#include <SDL_audio.h>
#include <metrics.h>
#include <samplerate.h>
#include <ring_buffer.h>
#define AUDIO_SAMPLE_RATE 48000
#define SYSTEM_SAMPLE_FORMAT AUDIO_F32SYS
#define SYSTEM_SAMPLE_SIZE 4
#define BYTES_PER_HALF_SECOND ((AUDIO_SAMPLE_RATE / 2) * SYSTEM_SAMPLE_SIZE)
static_assert(sizeof(float) == 4, "float must be 32 bits");
#define S16_TO_F32(x) ((float)(x) / (float)32768)
#define HOST_SAMPLE_RATE 48000
#define HOST_SAMPLE_FORMAT AUDIO_F32SYS
#define HOST_SAMPLE_SIZE sizeof(float)
//#define HOST_BUFFER_SIZE ((HOST_SAMPLE_RATE) / 2)
#define HOST_BUFFER_SIZE 16384
#define GUEST_SAMPLE_SIZE sizeof(float)
// Variable, controlled by the game
unsigned guest_sample_rate = HOST_SAMPLE_RATE;
// output_sample_rate / input_sample_rate
double resample_ratio = 1;
#define FRAMES_PER_REQUEST 1024
#define AUDIO_CHANNELS 2
#define GUEST_BUFFER_SIZE ((FRAMES_PER_REQUEST) * (AUDIO_CHANNELS))
static SDL_AudioStream* audio_stream = NULL;
SDL_mutex* audio_stream_mutex;
SDL_AudioSpec audio_spec;
SDL_AudioSpec request;
SDL_AudioDeviceID audio_dev;
INLINE void acquire_audiostream_mutex() {
SDL_LockMutex(audio_stream_mutex);
}
float guest_sample_buffer[GUEST_BUFFER_SIZE];
INLINE void release_audiostream_mutex() {
SDL_UnlockMutex(audio_stream_mutex);
}
// Much larger than needed
#define TEMP_RESAMPLED_BUFFER_SIZE HOST_SAMPLE_RATE
#define TEMP_RESAMPLED_BUFFER_FRAMES (HOST_SAMPLE_RATE / AUDIO_CHANNELS)
float temp_resampled_buffer[TEMP_RESAMPLED_BUFFER_SIZE];
unsigned idx_guest_sample_buffer = 0;
ring_buffer_t host_sample_buffer;
SRC_STATE* resampler;
void audio_callback(void* userdata, Uint8* stream, int length) {
int gotten = 0;
acquire_audiostream_mutex();
int available = SDL_AudioStreamAvailable(audio_stream);
set_metric(METRIC_AUDIOSTREAM_AVAILABLE, available);
if (available > 0) {
gotten = SDL_AudioStreamGet(audio_stream, stream, length);
}
release_audiostream_mutex();
int gotten_samples = gotten / sizeof(float);
float* out = (float*)stream;
out += gotten_samples;
/*
if (gotten_samples < length / sizeof(float)) {
logwarn("AudioStream buffer underflow! You may hear crackling! We're %d bytes short.", length - gotten);
}
*/
for (int i = gotten_samples; i < length / sizeof(float); i++) {
float sample = 0;
*out++ = sample;
set_metric(METRIC_AUDIOSTREAM_AVAILABLE, host_sample_buffer.size);
float* f_stream = (float*)stream;
for (int i = 0; i < length; i += HOST_SAMPLE_SIZE) {
float sample = ring_buffer_pop(&host_sample_buffer);
*f_stream++ = sample;
}
}
void audio_init() {
adjust_audio_sample_rate(AUDIO_SAMPLE_RATE);
memset(temp_resampled_buffer, 0, TEMP_RESAMPLED_BUFFER_SIZE * HOST_SAMPLE_SIZE);
ring_buffer_init(&host_sample_buffer, HOST_BUFFER_SIZE);
adjust_audio_sample_rate(HOST_SAMPLE_RATE);
memset(&request, 0, sizeof(request));
request.freq = AUDIO_SAMPLE_RATE;
request.format = SYSTEM_SAMPLE_FORMAT;
request.channels = 2;
request.samples = 1024;
request.freq = HOST_SAMPLE_RATE;
request.format = HOST_SAMPLE_FORMAT;
request.channels = AUDIO_CHANNELS;
request.samples = FRAMES_PER_REQUEST;
request.callback = audio_callback;
request.userdata = NULL;
audio_dev = SDL_OpenAudioDevice(NULL, 0, &request, &audio_spec, 0);
audio_dev = SDL_OpenAudioDevice(NULL, 0, &request, &audio_spec, 0);
unimplemented(request.format != audio_spec.format, "Request != got");
unimplemented(request.format != audio_spec.format, "Request format != got");
unimplemented(request.freq != audio_spec.freq, "Request freq %d != got freq %d", request.freq, audio_spec.freq);
if (audio_dev == 0) {
logfatal("Failed to initialize SDL audio: %s", SDL_GetError());
@ -69,33 +73,54 @@ void audio_init() {
SDL_PauseAudioDevice(audio_dev, false);
audio_stream_mutex = SDL_CreateMutex();
if (!audio_stream_mutex) {
logfatal("Unable to initialize mutex");
int src_error = 0;
resampler = src_new(SRC_SINC_BEST_QUALITY, AUDIO_CHANNELS, &src_error);
if (resampler == NULL) {
logfatal("Failed to initialize libsamplerate! Error: %d", src_error);
}
}
void flush_guest_buffer() {
if (idx_guest_sample_buffer == 0) {
return;
}
SRC_DATA resampler_data = { 0 };
resampler_data.data_in = guest_sample_buffer;
resampler_data.input_frames = idx_guest_sample_buffer / AUDIO_CHANNELS;
resampler_data.data_out = temp_resampled_buffer;
resampler_data.output_frames = TEMP_RESAMPLED_BUFFER_FRAMES;
resampler_data.src_ratio = resample_ratio;
resampler_data.end_of_input = false;
int error = src_process(resampler, &resampler_data);
if (error != 0) {
logalways("Error resampling! %s", src_strerror(error));
} else {
loginfo("Input frames: %ld Input frames used: %ld Output frames: %ld", resampler_data.input_frames, resampler_data.input_frames_used, resampler_data.output_frames_gen);
for (int i = 0; i < resampler_data.output_frames_gen * AUDIO_CHANNELS; i++) {
ring_buffer_push_blocking(&host_sample_buffer, temp_resampled_buffer[i]);
}
}
idx_guest_sample_buffer = 0;
}
void adjust_audio_sample_rate(int sample_rate) {
logwarn("Adjusting audio sample rate, locking mutex!");
acquire_audiostream_mutex();
if (audio_stream != NULL) {
SDL_FreeAudioStream(audio_stream);
}
// Flush first so all samples already pushed are resampled at the old rate
flush_guest_buffer();
audio_stream = SDL_NewAudioStream(AUDIO_S16SYS, 2, sample_rate, SYSTEM_SAMPLE_FORMAT, 2, AUDIO_SAMPLE_RATE);
release_audiostream_mutex();
guest_sample_rate = sample_rate;
resample_ratio = ((double)HOST_SAMPLE_RATE) / ((double)guest_sample_rate);
logalways("Adjusting guest sample rate. Host rate: %d Guest rate: %d Ratio: %f", HOST_SAMPLE_RATE, guest_sample_rate, resample_ratio);
}
void audio_push_sample(s16 left, s16 right) {
s16 samples[2] = {
left,
right
};
int available_bytes = SDL_AudioStreamAvailable(audio_stream);
if (available_bytes < BYTES_PER_HALF_SECOND) {
SDL_AudioStreamPut(audio_stream, samples, 2 * sizeof(s16));
} else {
logwarn("Not pushing sample, there are already %d bytes available.", available_bytes);
if (idx_guest_sample_buffer + 2 > GUEST_BUFFER_SIZE) {
// resample and push to host buffer
flush_guest_buffer();
}
guest_sample_buffer[idx_guest_sample_buffer++] = S16_TO_F32(left);
guest_sample_buffer[idx_guest_sample_buffer++] = S16_TO_F32(right);
}

View file

@ -76,6 +76,7 @@ u32 read_word_aireg(u32 address) {
void sample() {
if (n64sys.ai.dma_count == 0) {
audio_push_sample(0, 0);
return;
}