SoundTouch resampler integration
This commit is contained in:
@@ -8,10 +8,6 @@
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#if defined(ARCH_X64)
|
||||
#include "emmintrin.h"
|
||||
#endif
|
||||
|
||||
LOG_CHANNEL(cellAudio);
|
||||
|
||||
vm::gvar<char, AUDIO_PORT_OFFSET * AUDIO_PORT_COUNT> g_audio_buffer;
|
||||
@@ -60,11 +56,7 @@ void cell_audio_config::reset(bool backend_changed)
|
||||
backend = Emu.GetCallbacks().get_audio();
|
||||
}
|
||||
|
||||
{
|
||||
std::string str;
|
||||
backend->dump_capabilities(str);
|
||||
cellAudio.notice("cellAudio initializing. Backend: %s, Capabilities: %s", backend->GetName(), str.c_str());
|
||||
}
|
||||
cellAudio.notice("cellAudio initializing. Backend: %s", backend->GetName());
|
||||
|
||||
const AudioFreq freq = AudioFreq::FREQ_48K;
|
||||
const AudioSampleSize sample_size = raw.convert_to_s16 ? AudioSampleSize::S16 : AudioSampleSize::FLOAT;
|
||||
@@ -85,14 +77,14 @@ void cell_audio_config::reset(bool backend_changed)
|
||||
|
||||
audio_channels = backend->get_channels();
|
||||
audio_sampling_rate = backend->get_sampling_rate();
|
||||
audio_block_period = AUDIO_BUFFER_SAMPLES * 1000000 / audio_sampling_rate;
|
||||
audio_block_period = AUDIO_BUFFER_SAMPLES * 1'000'000 / audio_sampling_rate;
|
||||
audio_sample_size = backend->get_sample_size();
|
||||
audio_min_buffer_duration = backend->GetCallbackFrameLen();
|
||||
audio_min_buffer_duration = backend->GetCallbackFrameLen() + u32{AUDIO_BUFFER_SAMPLES} * 2.0 / audio_sampling_rate; // Add 2 blocks to allow jitter compensation
|
||||
|
||||
audio_buffer_length = AUDIO_BUFFER_SAMPLES * audio_channels;
|
||||
audio_buffer_size = audio_buffer_length * audio_sample_size;
|
||||
|
||||
desired_buffer_duration = raw.desired_buffer_duration * 1000llu;
|
||||
desired_buffer_duration = std::max(static_cast<s64>(audio_min_buffer_duration * 1000), raw.desired_buffer_duration) * 1000llu;
|
||||
buffering_enabled = raw.buffering_enabled && raw.renderer != audio_renderer::null;
|
||||
|
||||
minimum_block_period = audio_block_period / 2;
|
||||
@@ -106,18 +98,18 @@ void cell_audio_config::reset(bool backend_changed)
|
||||
|
||||
const bool raw_time_stretching_enabled = buffering_enabled && raw.enable_time_stretching && (raw.time_stretching_threshold > 0);
|
||||
|
||||
time_stretching_enabled = raw_time_stretching_enabled && backend->has_capability(AudioBackend::SET_FREQUENCY_RATIO);
|
||||
|
||||
time_stretching_enabled = raw_time_stretching_enabled;
|
||||
time_stretching_threshold = raw.time_stretching_threshold / 100.0f;
|
||||
|
||||
// Warn if audio backend does not support all requested features
|
||||
if (raw.buffering_enabled && !buffering_enabled)
|
||||
{
|
||||
cellAudio.error("Audio backend %s does not support buffering, this option will be ignored.", backend->GetName());
|
||||
}
|
||||
if (raw_time_stretching_enabled && !time_stretching_enabled)
|
||||
{
|
||||
cellAudio.error("Audio backend %s does not support time stretching, this option will be ignored.", backend->GetName());
|
||||
|
||||
if (raw.enable_time_stretching)
|
||||
{
|
||||
cellAudio.error("Audio backend %s does not support time stretching, this option will be ignored.", backend->GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +117,6 @@ audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg)
|
||||
: backend(_cfg.backend)
|
||||
, cfg(_cfg)
|
||||
, buf_sz(AUDIO_BUFFER_SAMPLES * _cfg.audio_channels)
|
||||
, emu_paused(Emu.IsPaused())
|
||||
{
|
||||
// Initialize buffers
|
||||
if (cfg.num_allocated_buffers > MAX_AUDIO_BUFFERS)
|
||||
@@ -139,23 +130,26 @@ audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg)
|
||||
}
|
||||
|
||||
// Init audio dumper if enabled
|
||||
if (g_cfg.audio.dump_to_file)
|
||||
if (cfg.raw.dump_to_file)
|
||||
{
|
||||
m_dump.reset(new AudioDumper(cfg.audio_channels, cfg.audio_sampling_rate, cfg.audio_sample_size));
|
||||
m_dump.Open(cfg.audio_channels, cfg.audio_sampling_rate, cfg.audio_sample_size);
|
||||
}
|
||||
|
||||
// Configure resampler
|
||||
resampler.set_params(static_cast<AudioChannelCnt>(cfg.audio_channels), AudioFreq::FREQ_48K);
|
||||
resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL);
|
||||
|
||||
const f64 buffer_dur_mult = [&]()
|
||||
{
|
||||
const f64 min_buf_dur = _cfg.audio_min_buffer_duration + 0.01; // Add 10ms to allow jitter compensation
|
||||
if (cfg.raw.buffering_enabled)
|
||||
if (cfg.buffering_enabled)
|
||||
{
|
||||
return std::max<f64>(min_buf_dur, cfg.raw.desired_buffer_duration / 1000.0 * 2); // Allocate 2x buffer to keep buffering algorithm happy
|
||||
return cfg.desired_buffer_duration / 1'000'000.0 + 0.02; // Add 20ms to buffer to keep buffering algorithm happy
|
||||
}
|
||||
|
||||
return min_buf_dur;
|
||||
return cfg.audio_min_buffer_duration;
|
||||
}();
|
||||
|
||||
cb_ringbuf.set_buf_size(static_cast<u32>(_cfg.audio_channels * _cfg.audio_sampling_rate * _cfg.audio_sample_size * buffer_dur_mult));
|
||||
cb_ringbuf.set_buf_size(static_cast<u32>(cfg.audio_channels * cfg.audio_sampling_rate * cfg.audio_sample_size * buffer_dur_mult));
|
||||
backend->SetWriteCallback(std::bind(&audio_ringbuffer::backend_write_callback, this, std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
@@ -171,19 +165,18 @@ audio_ringbuffer::~audio_ringbuffer()
|
||||
|
||||
f32 audio_ringbuffer::set_frequency_ratio(f32 new_ratio)
|
||||
{
|
||||
if (!has_capability(AudioBackend::SET_FREQUENCY_RATIO))
|
||||
{
|
||||
ensure(new_ratio == 1.0f);
|
||||
frequency_ratio = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
frequency_ratio = backend->SetFrequencyRatio(new_ratio);
|
||||
//cellAudio.trace("set_frequency_ratio(%1.2f) -> %1.2f", new_ratio, frequency_ratio);
|
||||
}
|
||||
frequency_ratio = resampler.set_tempo(new_ratio);
|
||||
|
||||
return frequency_ratio;
|
||||
}
|
||||
|
||||
float* audio_ringbuffer::get_buffer(u32 num) const
|
||||
{
|
||||
AUDIT(num < cfg.num_allocated_buffers);
|
||||
AUDIT(buffer[num]);
|
||||
return buffer[num].get();
|
||||
}
|
||||
|
||||
u32 audio_ringbuffer::backend_write_callback(u32 size, void *buf)
|
||||
{
|
||||
if (!backend_active.observe()) backend_active = true;
|
||||
@@ -196,50 +189,96 @@ u64 audio_ringbuffer::get_timestamp()
|
||||
return get_system_time();
|
||||
}
|
||||
|
||||
void audio_ringbuffer::enqueue(const float* in_buffer)
|
||||
float* audio_ringbuffer::get_current_buffer() const
|
||||
{
|
||||
return get_buffer(cur_pos);
|
||||
}
|
||||
|
||||
u64 audio_ringbuffer::get_enqueued_samples() const
|
||||
{
|
||||
AUDIT(cfg.buffering_enabled);
|
||||
const u64 ringbuf_samples = cb_ringbuf.get_used_size() / (cfg.audio_sample_size * cfg.audio_channels);
|
||||
|
||||
if (cfg.time_stretching_enabled)
|
||||
{
|
||||
return ringbuf_samples + resampler.samples_available();
|
||||
}
|
||||
|
||||
return ringbuf_samples;
|
||||
}
|
||||
|
||||
u64 audio_ringbuffer::get_enqueued_playtime() const
|
||||
{
|
||||
AUDIT(cfg.buffering_enabled);
|
||||
|
||||
return get_enqueued_samples() * 1'000'000 / cfg.audio_sampling_rate;
|
||||
}
|
||||
|
||||
void audio_ringbuffer::enqueue(bool enqueue_silence, bool force)
|
||||
{
|
||||
AUDIT(cur_pos < cfg.num_allocated_buffers);
|
||||
|
||||
// Prepare buffer
|
||||
const void* buf = in_buffer;
|
||||
static float silence_buffer[u32{AUDIO_MAX_CHANNELS_COUNT} * u32{AUDIO_BUFFER_SAMPLES}]{};
|
||||
float* buf = silence_buffer;
|
||||
|
||||
if (buf == nullptr)
|
||||
if (!enqueue_silence)
|
||||
{
|
||||
buf = buffer[cur_pos].get();
|
||||
cur_pos = (cur_pos + 1) % cfg.num_allocated_buffers;
|
||||
}
|
||||
|
||||
// Dump audio if enabled
|
||||
if (m_dump)
|
||||
{
|
||||
m_dump->WriteData(buf, cfg.audio_buffer_size);
|
||||
}
|
||||
m_dump.WriteData(buf, cfg.audio_buffer_size);
|
||||
|
||||
enqueued_samples += AUDIO_BUFFER_SAMPLES;
|
||||
|
||||
// Start playing audio
|
||||
play();
|
||||
|
||||
if (!backend_active.observe())
|
||||
if (!backend_active.observe() && !force)
|
||||
{
|
||||
// backend is not ready yet
|
||||
return;
|
||||
}
|
||||
|
||||
// Enqueue audio
|
||||
const u32 data_size = AUDIO_BUFFER_SAMPLES * cfg.audio_sample_size * cfg.audio_channels;
|
||||
|
||||
if (cb_ringbuf.get_free_size() >= data_size)
|
||||
if (cfg.time_stretching_enabled)
|
||||
{
|
||||
cb_ringbuf.push(buf, data_size);
|
||||
resampler.put_samples(buf, AUDIO_BUFFER_SAMPLES);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Since time stretching step is skipped, we can commit to buffer directly
|
||||
commit_data(buf, AUDIO_BUFFER_SAMPLES);
|
||||
}
|
||||
}
|
||||
|
||||
void audio_ringbuffer::enqueue_silence(u32 buf_count)
|
||||
void audio_ringbuffer::enqueue_silence(u32 buf_count, bool force)
|
||||
{
|
||||
for (u32 i = 0; i < buf_count; i++)
|
||||
{
|
||||
enqueue(silence_buffer);
|
||||
enqueue(true, force);
|
||||
}
|
||||
}
|
||||
|
||||
void audio_ringbuffer::process_resampled_data()
|
||||
{
|
||||
if (!cfg.time_stretching_enabled) return;
|
||||
|
||||
const auto samples = resampler.get_samples(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * cfg.audio_channels));
|
||||
commit_data(samples.first, samples.second);
|
||||
}
|
||||
|
||||
void audio_ringbuffer::commit_data(f32* buf, u32 sample_cnt)
|
||||
{
|
||||
sample_cnt *= cfg.audio_channels;
|
||||
|
||||
if (cfg.backend->get_convert_to_s16())
|
||||
{
|
||||
AudioBackend::convert_to_s16(sample_cnt, buf, buf);
|
||||
}
|
||||
|
||||
sample_cnt *= cfg.audio_sample_size;
|
||||
|
||||
if (cb_ringbuf.get_free_size() >= sample_cnt)
|
||||
{
|
||||
cb_ringbuf.push(buf, sample_cnt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,82 +289,44 @@ void audio_ringbuffer::play()
|
||||
return;
|
||||
}
|
||||
|
||||
if (frequency_ratio != 1.0f)
|
||||
{
|
||||
set_frequency_ratio(1.0f);
|
||||
}
|
||||
|
||||
playing = true;
|
||||
|
||||
ensure(enqueued_samples > 0);
|
||||
|
||||
play_timestamp = get_timestamp();
|
||||
backend->Play();
|
||||
}
|
||||
|
||||
void audio_ringbuffer::flush()
|
||||
{
|
||||
//cellAudio.trace("Flushing an estimated %llu enqueued samples", enqueued_samples);
|
||||
|
||||
backend->Pause();
|
||||
cb_ringbuf.flush();
|
||||
resampler.flush();
|
||||
backend_active = false;
|
||||
playing = false;
|
||||
|
||||
if (frequency_ratio != 1.0f)
|
||||
if (frequency_ratio != RESAMPLER_MAX_FREQ_VAL)
|
||||
{
|
||||
set_frequency_ratio(1.0f);
|
||||
frequency_ratio = set_frequency_ratio(RESAMPLER_MAX_FREQ_VAL);
|
||||
}
|
||||
|
||||
enqueued_samples = 0;
|
||||
}
|
||||
|
||||
u64 audio_ringbuffer::update()
|
||||
u64 audio_ringbuffer::update(bool emu_is_paused)
|
||||
{
|
||||
// Check emulator pause state
|
||||
if (Emu.IsPaused())
|
||||
if (emu_is_paused)
|
||||
{
|
||||
// Emulator paused
|
||||
if (playing)
|
||||
{
|
||||
flush();
|
||||
}
|
||||
emu_paused = true;
|
||||
}
|
||||
else if (emu_paused)
|
||||
else
|
||||
{
|
||||
// Emulator unpaused
|
||||
if (enqueued_samples > 0)
|
||||
{
|
||||
play();
|
||||
}
|
||||
emu_paused = false;
|
||||
play();
|
||||
}
|
||||
|
||||
// Prepare timestamp and playing status
|
||||
// Prepare timestamp
|
||||
const u64 timestamp = get_timestamp();
|
||||
const bool new_playing = !emu_paused && get_backend_playing();
|
||||
|
||||
// Calculate how many audio samples have played since last time
|
||||
if (cfg.buffering_enabled && (playing || new_playing))
|
||||
{
|
||||
enqueued_samples = cb_ringbuf.get_used_size() / (cfg.audio_sample_size * cfg.audio_channels);
|
||||
}
|
||||
|
||||
// Update playing state
|
||||
if (playing != new_playing)
|
||||
{
|
||||
if (!new_playing)
|
||||
{
|
||||
cellAudio.warning("Audio backend stopped unexpectedly, likely due to a buffer underrun");
|
||||
|
||||
flush();
|
||||
playing = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
playing = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Store and return timestamp
|
||||
update_timestamp = timestamp;
|
||||
@@ -445,15 +446,14 @@ void cell_audio_thread::reset_ports(s32 offset)
|
||||
}
|
||||
}
|
||||
|
||||
void cell_audio_thread::advance(u64 timestamp, bool reset)
|
||||
void cell_audio_thread::advance(u64 timestamp)
|
||||
{
|
||||
ringbuffer->process_resampled_data();
|
||||
|
||||
std::unique_lock lock(mutex);
|
||||
|
||||
// update ports
|
||||
if (reset)
|
||||
{
|
||||
reset_ports(0);
|
||||
}
|
||||
reset_ports(0);
|
||||
|
||||
for (auto& port : ports)
|
||||
{
|
||||
@@ -470,9 +470,7 @@ void cell_audio_thread::advance(u64 timestamp, bool reset)
|
||||
if (cfg.buffering_enabled)
|
||||
{
|
||||
// Calculate rolling average of enqueued playtime
|
||||
const u64 enqueued_playtime = ringbuffer->get_enqueued_playtime(/* raw */ true);
|
||||
m_average_playtime = cfg.period_average_alpha * enqueued_playtime + (1.0f - cfg.period_average_alpha) * m_average_playtime;
|
||||
//cellAudio.error("m_average_playtime=%4.2f, enqueued_playtime=%u", m_average_playtime, enqueued_playtime);
|
||||
m_average_playtime = cfg.period_average_alpha * ringbuffer->get_enqueued_playtime() + (1.0f - cfg.period_average_alpha) * m_average_playtime;
|
||||
}
|
||||
|
||||
m_counter++;
|
||||
@@ -539,6 +537,7 @@ namespace audio
|
||||
.enable_time_stretching = static_cast<bool>(g_cfg.audio.enable_time_stretching),
|
||||
.time_stretching_threshold = g_cfg.audio.time_stretching_threshold,
|
||||
.convert_to_s16 = static_cast<bool>(g_cfg.audio.convert_to_s16),
|
||||
.dump_to_file = static_cast<bool>(g_cfg.audio.dump_to_file),
|
||||
.downmix = g_cfg.audio.audio_channel_downmix,
|
||||
.renderer = g_cfg.audio.renderer,
|
||||
.provider = g_cfg.audio.provider
|
||||
@@ -564,7 +563,8 @@ namespace audio
|
||||
raw.enable_time_stretching != new_raw.enable_time_stretching ||
|
||||
raw.convert_to_s16 != new_raw.convert_to_s16 ||
|
||||
raw.downmix != new_raw.downmix ||
|
||||
raw.renderer != new_raw.renderer)
|
||||
raw.renderer != new_raw.renderer ||
|
||||
raw.dump_to_file != new_raw.dump_to_file)
|
||||
{
|
||||
g_audio.cfg.raw = new_raw;
|
||||
g_audio.m_update_configuration = raw.renderer != new_raw.renderer ? audio_backend_update::ALL : audio_backend_update::PARAM;
|
||||
@@ -597,6 +597,7 @@ void cell_audio_thread::reset_counters()
|
||||
m_last_period_end = m_start_time;
|
||||
m_dynamic_period = 0;
|
||||
m_backend_failed = false;
|
||||
m_audio_should_restart = true;
|
||||
}
|
||||
|
||||
cell_audio_thread::cell_audio_thread()
|
||||
@@ -658,10 +659,13 @@ void cell_audio_thread::operator()()
|
||||
m_backend_failed = false;
|
||||
}
|
||||
|
||||
const u64 timestamp = ringbuffer->update();
|
||||
const bool emu_paused = Emu.IsPaused();
|
||||
const u64 timestamp = ringbuffer->update(emu_paused);
|
||||
|
||||
if (Emu.IsPaused())
|
||||
if (emu_paused)
|
||||
{
|
||||
m_audio_should_restart = true;
|
||||
ringbuffer->flush();
|
||||
thread_ctrl::wait_for(10000);
|
||||
continue;
|
||||
}
|
||||
@@ -670,6 +674,30 @@ void cell_audio_thread::operator()()
|
||||
|
||||
const u64 time_since_last_period = timestamp - m_last_period_end;
|
||||
|
||||
// Handle audio restart
|
||||
if (m_audio_should_restart)
|
||||
{
|
||||
// align to 5.(3)ms on global clock - some games seem to prefer this
|
||||
const s64 audio_period_alignment_delta = (timestamp - m_start_time) % cfg.audio_block_period;
|
||||
if (audio_period_alignment_delta > cfg.period_comparison_margin)
|
||||
{
|
||||
thread_ctrl::wait_for(audio_period_alignment_delta - cfg.period_comparison_margin);
|
||||
}
|
||||
|
||||
if (cfg.buffering_enabled)
|
||||
{
|
||||
// Restart algorithm
|
||||
cellAudio.trace("restarting audio");
|
||||
ringbuffer->enqueue_silence(cfg.desired_full_buffers, true);
|
||||
finish_port_volume_stepping();
|
||||
m_average_playtime = static_cast<f32>(ringbuffer->get_enqueued_playtime());
|
||||
untouched_expected = 0;
|
||||
}
|
||||
|
||||
m_audio_should_restart = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cfg.buffering_enabled)
|
||||
{
|
||||
const u64 period_end = (m_counter * cfg.audio_block_period) + m_start_time;
|
||||
@@ -684,95 +712,72 @@ void cell_audio_thread::operator()()
|
||||
else
|
||||
{
|
||||
const u64 enqueued_samples = ringbuffer->get_enqueued_samples();
|
||||
f32 frequency_ratio = ringbuffer->get_frequency_ratio();
|
||||
u64 enqueued_playtime = ringbuffer->get_enqueued_playtime();
|
||||
const f32 frequency_ratio = ringbuffer->get_frequency_ratio();
|
||||
const u64 enqueued_playtime = ringbuffer->get_enqueued_playtime();
|
||||
const u64 enqueued_buffers = enqueued_samples / AUDIO_BUFFER_SAMPLES;
|
||||
|
||||
const bool playing = ringbuffer->is_playing();
|
||||
|
||||
const auto tag_info = count_port_buffer_tags();
|
||||
const u32 active_ports = std::get<0>(tag_info);
|
||||
const u32 in_progress = std::get<1>(tag_info);
|
||||
const u32 untouched = std::get<2>(tag_info);
|
||||
const u32 incomplete = std::get<3>(tag_info);
|
||||
|
||||
// Wait for a dynamic period - try to maintain an average as close as possible to 5.(3)ms
|
||||
if (!playing)
|
||||
// Ratio between the rolling average of the audio period, and the desired audio period
|
||||
const f32 average_playtime_ratio = m_average_playtime / cfg.audio_buffer_length;
|
||||
|
||||
// Use the above average ratio to decide how much buffer we should be aiming for
|
||||
f32 desired_duration_adjusted = cfg.desired_buffer_duration + (cfg.audio_block_period / 2.0f);
|
||||
if (average_playtime_ratio < 1.0f)
|
||||
{
|
||||
// When the buffer is empty, always use the correct block period
|
||||
m_dynamic_period = cfg.audio_block_period;
|
||||
desired_duration_adjusted /= std::max(average_playtime_ratio, 0.25f);
|
||||
}
|
||||
else
|
||||
|
||||
if (cfg.time_stretching_enabled)
|
||||
{
|
||||
// Ratio between the rolling average of the audio period, and the desired audio period
|
||||
const f32 average_playtime_ratio = m_average_playtime / cfg.audio_buffer_length;
|
||||
|
||||
// Use the above average ratio to decide how much buffer we should be aiming for
|
||||
f32 desired_duration_adjusted = cfg.desired_buffer_duration + (cfg.audio_block_period / 2.0f);
|
||||
if (average_playtime_ratio < 1.0f)
|
||||
{
|
||||
desired_duration_adjusted /= std::max(average_playtime_ratio, 0.25f);
|
||||
}
|
||||
|
||||
if (cfg.time_stretching_enabled)
|
||||
{
|
||||
// Calculate what the playtime is without a frequency ratio
|
||||
const u64 raw_enqueued_playtime = ringbuffer->get_enqueued_playtime(/* raw= */ true);
|
||||
|
||||
// 1.0 means exactly as desired
|
||||
// <1.0 means not as full as desired
|
||||
// >1.0 means more full than desired
|
||||
const f32 desired_duration_rate = raw_enqueued_playtime / desired_duration_adjusted;
|
||||
|
||||
// update frequency ratio if necessary
|
||||
f32 new_ratio = frequency_ratio;
|
||||
if (desired_duration_rate < cfg.time_stretching_threshold)
|
||||
{
|
||||
const f32 normalized_desired_duration_rate = desired_duration_rate / cfg.time_stretching_threshold;
|
||||
const f32 request_ratio = normalized_desired_duration_rate * cfg.time_stretching_scale;
|
||||
AUDIT(request_ratio <= 1.0f);
|
||||
|
||||
// change frequency ratio in steps
|
||||
if (std::abs(frequency_ratio - request_ratio) > cfg.time_stretching_step)
|
||||
{
|
||||
new_ratio = ringbuffer->set_frequency_ratio(request_ratio);
|
||||
}
|
||||
}
|
||||
else if (frequency_ratio != 1.0f)
|
||||
{
|
||||
new_ratio = ringbuffer->set_frequency_ratio(1.0f);
|
||||
}
|
||||
|
||||
if (new_ratio != frequency_ratio)
|
||||
{
|
||||
// ratio changed, calculate new dynamic period
|
||||
frequency_ratio = new_ratio;
|
||||
enqueued_playtime = ringbuffer->get_enqueued_playtime();
|
||||
m_dynamic_period = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 1.0 means exactly as desired
|
||||
// <1.0 means not as full as desired
|
||||
// >1.0 means more full than desired
|
||||
const f32 desired_duration_rate = enqueued_playtime / desired_duration_adjusted;
|
||||
|
||||
if (desired_duration_rate >= 1.0f)
|
||||
// update frequency ratio if necessary
|
||||
if (desired_duration_rate < cfg.time_stretching_threshold)
|
||||
{
|
||||
// more full than desired
|
||||
const f32 multiplier = 1.0f / desired_duration_rate;
|
||||
m_dynamic_period = cfg.maximum_block_period - static_cast<u64>((cfg.maximum_block_period - cfg.audio_block_period) * multiplier);
|
||||
const f32 normalized_desired_duration_rate = desired_duration_rate / cfg.time_stretching_threshold;
|
||||
const f32 request_ratio = normalized_desired_duration_rate * cfg.time_stretching_scale;
|
||||
AUDIT(request_ratio <= RESAMPLER_MAX_FREQ_VAL);
|
||||
|
||||
// change frequency ratio in steps
|
||||
const f32 req_time_stretching_step = (request_ratio + frequency_ratio) / 2.0f;
|
||||
if (req_time_stretching_step > cfg.time_stretching_step)
|
||||
{
|
||||
ringbuffer->set_frequency_ratio(req_time_stretching_step);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (frequency_ratio != RESAMPLER_MAX_FREQ_VAL)
|
||||
{
|
||||
// not as full as desired
|
||||
const f32 multiplier = desired_duration_rate * desired_duration_rate; // quite aggressive, but helps more times than it hurts
|
||||
m_dynamic_period = cfg.minimum_block_period + static_cast<u64>((cfg.audio_block_period - cfg.minimum_block_period) * multiplier);
|
||||
ringbuffer->set_frequency_ratio(RESAMPLER_MAX_FREQ_VAL);
|
||||
}
|
||||
}
|
||||
|
||||
s64 time_left = m_dynamic_period - time_since_last_period;
|
||||
// 1.0 means exactly as desired
|
||||
// <1.0 means not as full as desired
|
||||
// >1.0 means more full than desired
|
||||
const f32 desired_duration_rate = enqueued_playtime / desired_duration_adjusted;
|
||||
|
||||
if (desired_duration_rate >= 1.0f)
|
||||
{
|
||||
// more full than desired
|
||||
const f32 multiplier = 1.0f / desired_duration_rate;
|
||||
m_dynamic_period = cfg.maximum_block_period - static_cast<u64>((cfg.maximum_block_period - cfg.audio_block_period) * multiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
// not as full as desired
|
||||
const f32 multiplier = desired_duration_rate * desired_duration_rate; // quite aggressive, but helps more times than it hurts
|
||||
m_dynamic_period = cfg.minimum_block_period + static_cast<u64>((cfg.audio_block_period - cfg.minimum_block_period) * multiplier);
|
||||
}
|
||||
|
||||
const s64 time_left = m_dynamic_period - time_since_last_period;
|
||||
if (time_left > cfg.period_comparison_margin)
|
||||
{
|
||||
thread_ctrl::wait_for(get_thread_wait_delay(time_left));
|
||||
@@ -784,10 +789,7 @@ void cell_audio_thread::operator()()
|
||||
{
|
||||
// no need to mix, just enqueue silence and advance time
|
||||
cellAudio.trace("enqueuing silence: no active ports, enqueued_buffers=%llu", enqueued_buffers);
|
||||
if (playing)
|
||||
{
|
||||
ringbuffer->enqueue_silence(1);
|
||||
}
|
||||
ringbuffer->enqueue_silence();
|
||||
untouched_expected = 0;
|
||||
advance(timestamp);
|
||||
continue;
|
||||
@@ -797,16 +799,6 @@ void cell_audio_thread::operator()()
|
||||
//cellAudio.error("active=%u, in_progress=%u, untouched=%u, incomplete=%u", active_ports, in_progress, untouched, incomplete);
|
||||
if (untouched > untouched_expected)
|
||||
{
|
||||
if (!playing)
|
||||
{
|
||||
// We ran out of buffer, probably because we waited too long
|
||||
// Don't enqueue anything, just advance time
|
||||
cellAudio.trace("advancing time: untouched=%u/%u (expected=%u), enqueued_buffers=0", untouched, active_ports, untouched_expected);
|
||||
untouched_expected = untouched;
|
||||
advance(timestamp);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Games may sometimes "skip" audio periods entirely if they're falling behind (a sort of "frameskip" for audio)
|
||||
// As such, if the game doesn't touch buffers for too long we advance time hoping the game recovers
|
||||
if (
|
||||
@@ -831,10 +823,7 @@ void cell_audio_thread::operator()()
|
||||
{
|
||||
// There's no audio in the buffers, simply advance time
|
||||
cellAudio.trace("enqueuing silence: untouched=%u/%u (expected=%u), enqueued_buffers=%llu", untouched, active_ports, untouched_expected, enqueued_buffers);
|
||||
if (playing)
|
||||
{
|
||||
ringbuffer->enqueue_silence(1);
|
||||
}
|
||||
ringbuffer->enqueue_silence();
|
||||
untouched_expected = untouched;
|
||||
advance(timestamp);
|
||||
continue;
|
||||
@@ -858,25 +847,6 @@ void cell_audio_thread::operator()()
|
||||
{
|
||||
cellAudio.trace("enqueueing: untouched=%u/%u (expected=%u), incomplete=%u/%u enqueued_buffers=%llu", untouched, active_ports, untouched_expected, incomplete, active_ports, enqueued_buffers);
|
||||
}
|
||||
|
||||
// Handle audio restart
|
||||
if (!playing)
|
||||
{
|
||||
// We are not playing (likely buffer underrun)
|
||||
// align to 5.(3)ms on global clock - some games seem to prefer this
|
||||
const s64 audio_period_alignment_delta = (timestamp - m_start_time) % cfg.audio_block_period;
|
||||
if (audio_period_alignment_delta > cfg.period_comparison_margin)
|
||||
{
|
||||
thread_ctrl::wait_for(audio_period_alignment_delta - cfg.period_comparison_margin);
|
||||
}
|
||||
|
||||
// Flush, add silence, restart algorithm
|
||||
cellAudio.trace("play/resume audio: received first audio buffer");
|
||||
ringbuffer->flush();
|
||||
ringbuffer->enqueue_silence(cfg.desired_full_buffers);
|
||||
finish_port_volume_stepping();
|
||||
m_average_playtime = static_cast<f32>(ringbuffer->get_enqueued_playtime());
|
||||
}
|
||||
}
|
||||
|
||||
// Mix
|
||||
@@ -1112,27 +1082,6 @@ void cell_audio_thread::mix(float *out_buffer, s32 offset)
|
||||
{
|
||||
std::memset(out_buffer, 0, out_buffer_sz * sizeof(float));
|
||||
}
|
||||
else if (cfg.backend->get_convert_to_s16())
|
||||
{
|
||||
// convert the data from float to s16 with clipping:
|
||||
// 2x MULPS
|
||||
// 2x MAXPS (optional)
|
||||
// 2x MINPS (optional)
|
||||
// 2x CVTPS2DQ (converts float to s32)
|
||||
// PACKSSDW (converts s32 to s16 with signed saturation)
|
||||
|
||||
#if defined(ARCH_X64)
|
||||
for (usz i = 0; i < out_buffer_sz; i += 8)
|
||||
{
|
||||
const auto scale = _mm_set1_ps(0x8000);
|
||||
_mm_store_ps(out_buffer + i / 2, _mm_castsi128_ps(_mm_packs_epi32(
|
||||
_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer + i), scale)),
|
||||
_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer + i + 4), scale)))));
|
||||
}
|
||||
#else
|
||||
fmt::throw_exception("Not supported");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void cell_audio_thread::finish_port_volume_stepping()
|
||||
|
||||
Reference in New Issue
Block a user