mirror of https://github.com/snes9xgit/snes9x.git
Add dynamic rate control for audio, based on Retroarch/Themaister's paper
Adds support to alsa and oss sound drivers on GTK+ port.
This commit is contained in:
parent
d89154a0b2
commit
39f05664cd
27
apu/apu.cpp
27
apu/apu.cpp
|
@ -199,7 +199,7 @@
|
|||
|
||||
#include "snes/snes.hpp"
|
||||
|
||||
#define APU_DEFAULT_INPUT_RATE 32000
|
||||
#define APU_DEFAULT_INPUT_RATE 31987 // 60hz
|
||||
#define APU_MINIMUM_SAMPLE_COUNT 512
|
||||
#define APU_MINIMUM_SAMPLE_BLOCK 128
|
||||
#define APU_NUMERATOR_NTSC 15664
|
||||
|
@ -240,6 +240,8 @@ namespace spc
|
|||
if necessary on game load. */
|
||||
static uint32 ratio_numerator = APU_NUMERATOR_NTSC;
|
||||
static uint32 ratio_denominator = APU_DENOMINATOR_NTSC;
|
||||
|
||||
static double dynamic_rate_multiplier = 1.0;
|
||||
}
|
||||
|
||||
namespace msu
|
||||
|
@ -274,7 +276,7 @@ static void DeStereo (uint8 *buffer, int sample_count)
|
|||
int16 *buf = (int16 *) buffer;
|
||||
int32 s1, s2;
|
||||
|
||||
for (int i = 0; i < sample_count >> 1; i++)
|
||||
for (int i = 0; i < (sample_count >> 1); i++)
|
||||
{
|
||||
s1 = (int32) buf[2 * i];
|
||||
s2 = (int32) buf[2 * i + 1];
|
||||
|
@ -348,7 +350,7 @@ bool8 S9xMixSamples (uint8 *buffer, int sample_count)
|
|||
if (msu::resampler->avail() >= sample_count)
|
||||
{
|
||||
msu::resampler->read((short *)msu::resample_buffer, sample_count);
|
||||
for (uint32 i = 0; i < sample_count; ++i)
|
||||
for (int i = 0; i < sample_count; ++i)
|
||||
*((int16*)(dest+(i * 2))) += *((int16*)(msu::resample_buffer +(i * 2)));
|
||||
}
|
||||
else // should never occur
|
||||
|
@ -471,12 +473,31 @@ void S9xSetSamplesAvailableCallback (apu_callback callback, void *data)
|
|||
spc::extra_data = data;
|
||||
}
|
||||
|
||||
void S9xUpdateDynamicRate (int avail, int buffer_size)
|
||||
{
|
||||
int half_size = buffer_size / 2;
|
||||
int delta_mid = avail - half_size;
|
||||
double direction = (double) delta_mid / half_size;
|
||||
spc::dynamic_rate_multiplier = 1.0 - (Settings.DynamicRateLimit / 100000.0) * direction;
|
||||
#if 1
|
||||
printf ("%d / %d : ", avail, buffer_size);
|
||||
printf ("%d%%\n", avail * 100 / buffer_size);
|
||||
#endif
|
||||
UpdatePlaybackRate();
|
||||
}
|
||||
|
||||
static void UpdatePlaybackRate (void)
|
||||
{
|
||||
if (Settings.SoundInputRate == 0)
|
||||
Settings.SoundInputRate = APU_DEFAULT_INPUT_RATE;
|
||||
|
||||
double time_ratio = (double) Settings.SoundInputRate * spc::timing_hack_numerator / (Settings.SoundPlaybackRate * spc::timing_hack_denominator);
|
||||
|
||||
if (Settings.DynamicRateControl)
|
||||
{
|
||||
time_ratio *= spc::dynamic_rate_multiplier;
|
||||
}
|
||||
|
||||
spc::resampler->time_ratio(time_ratio);
|
||||
|
||||
if (Settings.MSU1)
|
||||
|
|
|
@ -229,5 +229,6 @@ void S9xFinalizeSamples (void);
|
|||
void S9xClearSamples (void);
|
||||
bool8 S9xMixSamples (uint8 *, int);
|
||||
void S9xSetSamplesAvailableCallback (apu_callback, void *);
|
||||
void S9xUpdateDynamicRate (int, int);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -52,7 +52,6 @@ class HermiteResampler : public Resampler
|
|||
time_ratio (double ratio)
|
||||
{
|
||||
r_step = ratio;
|
||||
clear ();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -83,7 +82,7 @@ class HermiteResampler : public Resampler
|
|||
while (r_frac <= 1.0 && o_position < num_samples)
|
||||
{
|
||||
hermite_val[0] = hermite (r_frac, r_left [0], r_left [1], r_left [2], r_left [3]);
|
||||
hermite_val[1] = hermite (r_frac, r_right[0], r_right[1], r_right[2], r_right[3]);
|
||||
hermite_val[1] = hermite (r_frac, r_right[0], r_right[1], r_right[2], r_right[3]);
|
||||
data[o_position] = SHORT_CLAMP (hermite_val[0]);
|
||||
data[o_position + 1] = SHORT_CLAMP (hermite_val[1]);
|
||||
|
||||
|
|
|
@ -65,7 +65,10 @@ bool8
|
|||
S9xAlsaSoundDriver::open_device (void)
|
||||
{
|
||||
int err;
|
||||
unsigned int periods = 8;
|
||||
unsigned int buffer_size = gui_config->sound_buffer_size * 1000;
|
||||
snd_pcm_sw_params_t *sw_params;
|
||||
snd_pcm_hw_params_t *hw_params;
|
||||
snd_pcm_uframes_t alsa_buffer_size, alsa_period_size;
|
||||
|
||||
printf ("ALSA sound driver initializing...\n");
|
||||
|
@ -89,28 +92,28 @@ S9xAlsaSoundDriver::open_device (void)
|
|||
Settings.SoundPlaybackRate,
|
||||
gui_config->sound_buffer_size);
|
||||
|
||||
if ((err = snd_pcm_set_params (pcm,
|
||||
Settings.SixteenBitSound ? SND_PCM_FORMAT_S16 : SND_PCM_FORMAT_U8,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED,
|
||||
Settings.Stereo ? 2 : 1,
|
||||
Settings.SoundPlaybackRate,
|
||||
1 /* Allow software resampling */,
|
||||
gui_config->sound_buffer_size * 1000))
|
||||
< 0)
|
||||
{
|
||||
snd_pcm_hw_params_alloca (&hw_params);
|
||||
snd_pcm_hw_params_any (pcm, hw_params);
|
||||
snd_pcm_hw_params_set_format (pcm, hw_params, Settings.SixteenBitSound ? SND_PCM_FORMAT_S16 : SND_PCM_FORMAT_U8);
|
||||
snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
snd_pcm_hw_params_set_channels (pcm, hw_params, Settings.Stereo ? 2 : 1);
|
||||
snd_pcm_hw_params_set_rate_near (pcm, hw_params, &Settings.SoundPlaybackRate, NULL);
|
||||
snd_pcm_hw_params_set_rate_resample (pcm, hw_params, 0);
|
||||
snd_pcm_hw_params_set_buffer_time_near (pcm, hw_params, &buffer_size, NULL);
|
||||
snd_pcm_hw_params_set_periods_near (pcm, hw_params, &periods, NULL);
|
||||
|
||||
if ((err = snd_pcm_hw_params (pcm, hw_params)) < 0)
|
||||
goto close_fail;
|
||||
}
|
||||
|
||||
snd_pcm_sw_params_alloca (&sw_params);
|
||||
snd_pcm_sw_params_current (pcm, sw_params);
|
||||
snd_pcm_get_params (pcm, &alsa_buffer_size, &alsa_period_size);
|
||||
/* Don't start until we're [nearly] full */
|
||||
snd_pcm_sw_params_set_start_threshold (pcm,
|
||||
sw_params,
|
||||
(alsa_buffer_size / alsa_period_size) * alsa_period_size);
|
||||
/* Transfer in blocks of period-size */
|
||||
snd_pcm_sw_params_set_avail_min (pcm, sw_params, alsa_period_size);
|
||||
snd_pcm_sw_params_set_start_threshold (pcm, sw_params, (alsa_buffer_size / 2));
|
||||
err = snd_pcm_sw_params (pcm, sw_params);
|
||||
|
||||
output_buffer_size = snd_pcm_frames_to_bytes (pcm, alsa_buffer_size);
|
||||
|
||||
if (err < 0)
|
||||
goto close_fail;
|
||||
|
||||
|
@ -143,17 +146,37 @@ S9xAlsaSoundDriver::samples_available (void)
|
|||
snd_pcm_sframes_t frames_written, frames;
|
||||
int bytes;
|
||||
|
||||
S9xFinalizeSamples ();
|
||||
frames = snd_pcm_avail (pcm);
|
||||
|
||||
if (Settings.DynamicRateControl)
|
||||
{
|
||||
S9xUpdateDynamicRate (snd_pcm_frames_to_bytes (pcm, frames),
|
||||
output_buffer_size);
|
||||
}
|
||||
|
||||
frames = snd_pcm_avail_update (pcm);
|
||||
if (frames < 0)
|
||||
{
|
||||
frames = snd_pcm_recover (pcm, frames, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
S9xFinalizeSamples ();
|
||||
|
||||
if (Settings.DynamicRateControl)
|
||||
{
|
||||
// Using rate control, we should always keep the emulator's sound buffers empty to
|
||||
// maintain an accurate measurement.
|
||||
if (frames < (S9xGetSampleCount () >> (Settings.Stereo ? 1 : 0)))
|
||||
{
|
||||
S9xClearSamples ();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
frames = MIN (frames, S9xGetSampleCount () >> (Settings.Stereo ? 1 : 0));
|
||||
|
||||
bytes = snd_pcm_frames_to_bytes (pcm, frames);
|
||||
|
||||
if (bytes <= 0)
|
||||
{
|
||||
return;
|
||||
|
|
|
@ -21,6 +21,7 @@ class S9xAlsaSoundDriver : public S9xSoundDriver
|
|||
snd_pcm_t *pcm;
|
||||
int sound_buffer_size;
|
||||
uint8 *sound_buffer;
|
||||
int output_buffer_size;
|
||||
};
|
||||
|
||||
#endif /* __GTK_SOUND_DRIVER_ALSA_H */
|
||||
|
|
|
@ -64,7 +64,7 @@ bool8
|
|||
S9xOSSSoundDriver::open_device (void)
|
||||
{
|
||||
int temp;
|
||||
int output_buffer_size;
|
||||
audio_buf_info info;
|
||||
|
||||
output_buffer_size = (gui_config->sound_buffer_size * Settings.SoundPlaybackRate) / 1000;
|
||||
|
||||
|
@ -72,8 +72,6 @@ S9xOSSSoundDriver::open_device (void)
|
|||
output_buffer_size *= 2;
|
||||
if (Settings.SixteenBitSound)
|
||||
output_buffer_size *= 2;
|
||||
if (output_buffer_size > 65536)
|
||||
output_buffer_size = 65536;
|
||||
if (output_buffer_size < 256)
|
||||
output_buffer_size = 256;
|
||||
|
||||
|
@ -84,7 +82,13 @@ S9xOSSSoundDriver::open_device (void)
|
|||
filedes = open ("/dev/dsp", O_WRONLY | O_NONBLOCK);
|
||||
|
||||
if (filedes < 0)
|
||||
goto fail;
|
||||
{
|
||||
printf ("Failed\n --> (Device: /dev/dsp1)...");
|
||||
filedes = open ("/dev/dsp1", O_WRONLY | O_NONBLOCK);
|
||||
|
||||
if (filedes < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
printf ("OK\n");
|
||||
|
||||
|
@ -132,9 +136,14 @@ S9xOSSSoundDriver::open_device (void)
|
|||
|
||||
/* OSS requires a power-of-two buffer size, first 16 bits are the number
|
||||
* of fragments to generate, second 16 are the respective power-of-two. */
|
||||
temp = (2 << 16) | (S9xSoundBase2log (output_buffer_size));
|
||||
temp = (8 << 16) | (S9xSoundBase2log (output_buffer_size / 8));
|
||||
|
||||
output_buffer_size = S9xSoundPowerof2 (temp & 0xffff);
|
||||
if (ioctl (filedes, SNDCTL_DSP_SETFRAGMENT, &temp) < 0)
|
||||
goto close_fail;
|
||||
|
||||
ioctl (filedes, SNDCTL_DSP_GETOSPACE, &info);
|
||||
|
||||
output_buffer_size = info.fragsize * info.fragstotal;
|
||||
|
||||
printf (" --> (Buffer size: %d bytes, %dms latency)...",
|
||||
output_buffer_size,
|
||||
|
@ -142,9 +151,6 @@ S9xOSSSoundDriver::open_device (void)
|
|||
>> (Settings.SixteenBitSound ? 1 : 0))
|
||||
/ (Settings.SoundPlaybackRate));
|
||||
|
||||
if (ioctl (filedes, SNDCTL_DSP_SETFRAGMENT, &temp) < 0)
|
||||
goto close_fail;
|
||||
|
||||
printf ("OK\n");
|
||||
|
||||
S9xSetSamplesAvailableCallback (oss_samples_available, this);
|
||||
|
@ -175,11 +181,27 @@ S9xOSSSoundDriver::samples_available (void)
|
|||
int bytes_to_write;
|
||||
int bytes_written;
|
||||
|
||||
ioctl (filedes, SNDCTL_DSP_GETOSPACE, &info);
|
||||
|
||||
if (Settings.DynamicRateControl || 1)
|
||||
{
|
||||
S9xUpdateDynamicRate (info.bytes, output_buffer_size);
|
||||
}
|
||||
|
||||
S9xFinalizeSamples ();
|
||||
|
||||
samples_to_write = S9xGetSampleCount ();
|
||||
|
||||
ioctl (filedes, SNDCTL_DSP_GETOSPACE, &info);
|
||||
if (Settings.DynamicRateControl)
|
||||
{
|
||||
// Using rate control, we should always keep the emulator's sound buffers empty to
|
||||
// maintain an accurate measurement.
|
||||
if (samples_to_write > (info.bytes >> (Settings.SixteenBitSound ? 1 : 0)))
|
||||
{
|
||||
S9xClearSamples ();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
samples_to_write = MIN (info.bytes >> (Settings.SixteenBitSound ? 1 : 0),
|
||||
samples_to_write);
|
||||
|
|
|
@ -20,6 +20,7 @@ class S9xOSSSoundDriver : public S9xSoundDriver
|
|||
int filedes;
|
||||
uint8 *sound_buffer;
|
||||
int sound_buffer_size;
|
||||
int output_buffer_size;
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue