mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
audio improvements
This commit is contained in:
@@ -3,10 +3,30 @@
|
||||
|
||||
#include "samplerate.h"
|
||||
|
||||
/*
|
||||
* Minimal libsamplerate replacement for Android.
|
||||
*
|
||||
* Uses linear interpolation for sample-rate conversion. Quality is lower
|
||||
* than the sinc resampler used on desktop, but the ratio is correctly
|
||||
* applied so voices recorded at rates other than 48 kHz (e.g. 22050 Hz
|
||||
* dialogue in Halo CE) play at the right pitch instead of chipmunk-fast.
|
||||
*
|
||||
* The original stub did: (void)ratio; — i.e. passed samples through
|
||||
* unchanged regardless of the conversion ratio, causing every sub-48 kHz
|
||||
* voice to play back at 48000/source_rate times normal speed.
|
||||
*/
|
||||
|
||||
struct SRC_STATE {
|
||||
src_callback_t cb;
|
||||
void *cb_data;
|
||||
int channels;
|
||||
void *cb_data;
|
||||
int channels;
|
||||
|
||||
/* Current input block (pointer owned by the callback, not by us). */
|
||||
float *buf;
|
||||
long buf_len; /* frames in buf */
|
||||
|
||||
/* Fractional read position within buf (advances by 1/ratio per output frame). */
|
||||
double buf_pos;
|
||||
};
|
||||
|
||||
SRC_STATE *src_callback_new(src_callback_t cb, int converter_type, int channels,
|
||||
@@ -23,40 +43,98 @@ SRC_STATE *src_callback_new(src_callback_t cb, int converter_type, int channels,
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
state->cb = cb;
|
||||
state->cb_data = cb_data;
|
||||
state->cb = cb;
|
||||
state->cb_data = cb_data;
|
||||
state->channels = channels;
|
||||
state->buf = NULL;
|
||||
state->buf_len = 0;
|
||||
state->buf_pos = 0.0;
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
* ratio = output_sample_rate / input_sample_rate
|
||||
* > 1 : upsampling (e.g. 22050 -> 48000, ratio ≈ 2.177)
|
||||
* < 1 : downsampling
|
||||
* = 1 : pass-through (still goes through the interpolator for simplicity)
|
||||
*
|
||||
* step = 1/ratio = input frames consumed per output frame produced.
|
||||
*/
|
||||
long src_callback_read(SRC_STATE *state, double ratio, long frames, float *data)
|
||||
{
|
||||
(void)ratio;
|
||||
if (!state || !state->cb || !data || frames <= 0) {
|
||||
if (!state || !state->cb || !data || frames <= 0 || ratio <= 0.0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float *in = NULL;
|
||||
long got = state->cb(state->cb_data, &in);
|
||||
if (got <= 0 || in == NULL) {
|
||||
return 0;
|
||||
const double step = 1.0 / ratio;
|
||||
long out = 0;
|
||||
|
||||
while (out < frames) {
|
||||
long idx = (long)state->buf_pos;
|
||||
|
||||
/* Refill the input buffer when we have consumed it. */
|
||||
if (state->buf == NULL || idx >= state->buf_len) {
|
||||
/*
|
||||
* Carry the fractional overshoot past the end of the old block
|
||||
* into the start of the new block. This preserves continuity
|
||||
* when ratio > 1 (step < 1) and we drain the buffer gradually.
|
||||
*/
|
||||
double carry = (state->buf_len > 0)
|
||||
? state->buf_pos - (double)state->buf_len
|
||||
: 0.0;
|
||||
if (carry < 0.0) {
|
||||
carry = 0.0;
|
||||
}
|
||||
|
||||
float *new_buf = NULL;
|
||||
long got = state->cb(state->cb_data, &new_buf);
|
||||
if (got <= 0 || new_buf == NULL) {
|
||||
break; /* source exhausted */
|
||||
}
|
||||
|
||||
state->buf = new_buf;
|
||||
state->buf_len = got;
|
||||
state->buf_pos = carry;
|
||||
idx = (long)state->buf_pos;
|
||||
|
||||
if (idx >= state->buf_len) {
|
||||
break; /* carry >= new block length — shouldn't happen */
|
||||
}
|
||||
}
|
||||
|
||||
/* Linear interpolation between sample[idx] and sample[idx+1]. */
|
||||
float alpha = (float)(state->buf_pos - (double)idx);
|
||||
long next_idx = idx + 1;
|
||||
|
||||
for (int ch = 0; ch < state->channels; ch++) {
|
||||
float s0 = state->buf[idx * state->channels + ch];
|
||||
float s1 = (next_idx < state->buf_len)
|
||||
? state->buf[next_idx * state->channels + ch]
|
||||
: s0; /* hold last sample at block boundary */
|
||||
data[out * state->channels + ch] = s0 + alpha * (s1 - s0);
|
||||
}
|
||||
|
||||
state->buf_pos += step;
|
||||
out++;
|
||||
}
|
||||
|
||||
long to_copy = frames < got ? frames : got;
|
||||
memcpy(data, in, sizeof(float) * to_copy * state->channels);
|
||||
return to_copy;
|
||||
return out;
|
||||
}
|
||||
|
||||
int src_reset(SRC_STATE *state)
|
||||
{
|
||||
(void)state;
|
||||
if (state) {
|
||||
state->buf = NULL;
|
||||
state->buf_len = 0;
|
||||
state->buf_pos = 0.0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *src_strerror(int error)
|
||||
{
|
||||
(void)error;
|
||||
return "libsamplerate stub";
|
||||
return "libsamplerate stub (linear)";
|
||||
}
|
||||
|
||||
void src_float_to_short_array(const float *in, short *out, int len)
|
||||
@@ -64,14 +142,10 @@ void src_float_to_short_array(const float *in, short *out, int len)
|
||||
if (!in || !out || len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
float v = in[i];
|
||||
if (v > 1.0f) {
|
||||
v = 1.0f;
|
||||
} else if (v < -1.0f) {
|
||||
v = -1.0f;
|
||||
}
|
||||
if (v > 1.0f) v = 1.0f;
|
||||
else if (v < -1.0f) v = -1.0f;
|
||||
out[i] = (short)(v * 32767.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,7 +565,7 @@ cleanup:
|
||||
struct EmulatorSettings {
|
||||
int surface_scale = 1; // 1, 2, or 3
|
||||
std::string tcg_thread = "multi"; // "single" or "multi"
|
||||
bool use_dsp = true;
|
||||
bool use_dsp = false;
|
||||
bool hrtf = true;
|
||||
bool cache_shaders = true;
|
||||
bool hard_fpu = true;
|
||||
@@ -776,7 +776,7 @@ static SetupFiles SyncSetupFiles() {
|
||||
|
||||
EmulatorSettings emuSettings;
|
||||
emuSettings.surface_scale = GetPrefInt(env, activity, "setting_surface_scale", 1);
|
||||
emuSettings.use_dsp = GetPrefBool(env, activity, "setting_use_dsp", true);
|
||||
emuSettings.use_dsp = GetPrefBool(env, activity, "setting_use_dsp", false);
|
||||
emuSettings.hrtf = GetPrefBool(env, activity, "setting_hrtf", true);
|
||||
emuSettings.cache_shaders = GetPrefBool(env, activity, "setting_cache_shaders", true);
|
||||
emuSettings.hard_fpu = GetPrefBool(env, activity, "setting_hard_fpu", true);
|
||||
|
||||
@@ -88,7 +88,7 @@ static void xemu_settings_apply_defaults(void)
|
||||
g_config.display.setup_nvidia_profile = true;
|
||||
|
||||
g_config.audio.vp.num_workers = 0;
|
||||
g_config.audio.use_dsp = true;
|
||||
g_config.audio.use_dsp = false;
|
||||
g_config.audio.hrtf = true;
|
||||
g_config.audio.volume_limit = 1.0;
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
private fun hideSystemUI() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// Android 11 (API 30) and above
|
||||
@Suppress("DEPRECATION")
|
||||
window.setDecorFitsSystemWindows(false)
|
||||
window.insetsController?.let { controller ->
|
||||
controller.hide(WindowInsets.Type.systemBars())
|
||||
|
||||
@@ -44,7 +44,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||
toggleThread.check(R.id.btn_thread_multi)
|
||||
}
|
||||
|
||||
switchDsp.isChecked = prefs.getBoolean("setting_use_dsp", true)
|
||||
switchDsp.isChecked = prefs.getBoolean("setting_use_dsp", false)
|
||||
switchHrtf.isChecked = prefs.getBoolean("setting_hrtf", true)
|
||||
switchShaders.isChecked = prefs.getBoolean("setting_cache_shaders", true)
|
||||
switchFpu.isChecked = prefs.getBoolean("setting_hard_fpu", true)
|
||||
|
||||
@@ -321,8 +321,8 @@ static void monitor_init(MCPXAPUState *d)
|
||||
int fifo_frames = 3;
|
||||
int audio_samples = 512;
|
||||
#ifdef __ANDROID__
|
||||
fifo_frames = 8;
|
||||
audio_samples = 1024;
|
||||
fifo_frames = 16;
|
||||
audio_samples = 512;
|
||||
fifo_frames = getenv_int_clamped("XEMU_ANDROID_AUDIO_FIFO_FRAMES", 3, 32,
|
||||
fifo_frames);
|
||||
audio_samples = getenv_int_clamped("XEMU_ANDROID_AUDIO_SAMPLES", 256, 4096,
|
||||
|
||||
Reference in New Issue
Block a user