mirror of
https://github.com/Dillonb/n64.git
synced 2024-05-17 03:20:34 -04:00
Switch resampler to libsamplerate, sync to audio
This commit is contained in:
parent
dd3c70e395
commit
ec2ed753ba
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
59
src/common/ring_buffer.h
Normal 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
|
1
src/contrib/libsamplerate
Submodule
1
src/contrib/libsamplerate
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit aee38d0bc797d0d1a3774ef574af1d5d248d2398
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ u32 read_word_aireg(u32 address) {
|
|||
|
||||
void sample() {
|
||||
if (n64sys.ai.dma_count == 0) {
|
||||
audio_push_sample(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue