1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-21 18:22:33 +01:00

Audio: prevent click at play/pause

Cubeb uses internal rate resampler and it's not being flushed during call to stream_stop.
This results in noticeable click when emulator is unpaused.
Reset last sample buffer on pause for all backends.
This commit is contained in:
Vestral 2022-01-11 20:40:06 +09:00 committed by Megamouse
parent 681bab558b
commit 7977fbb9c5
7 changed files with 38 additions and 35 deletions

View File

@ -8,7 +8,6 @@ enum : u32
DEFAULT_AUDIO_SAMPLING_RATE = 48000,
MAX_AUDIO_BUFFERS = 64,
AUDIO_BUFFER_SAMPLES = 256,
AUDIO_MIN_LATENCY = 512,
AUDIO_MAX_CHANNELS = 8,
};

View File

@ -69,6 +69,7 @@ void CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChanne
m_sampling_rate = freq;
m_sample_size = sample_size;
m_channels = ch_cnt;
full_sample_size = get_channels() * get_sample_size();
cubeb_stream_params stream_param{};
stream_param.format = get_convert_to_s16() ? CUBEB_SAMPLE_S16NE : CUBEB_SAMPLE_FLOAT32NE;
@ -93,7 +94,8 @@ void CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChanne
Cubeb.error("cubeb_get_min_latency() failed: 0x%08x", err);
}
if (int err = cubeb_stream_init(m_ctx, &m_stream, "Main stream", nullptr, nullptr, nullptr, &stream_param, std::max<u32>(AUDIO_MIN_LATENCY, min_latency), data_cb, state_cb, this))
const u32 stream_latency = std::max(static_cast<u32>(AUDIO_MIN_LATENCY * get_sampling_rate()), min_latency);
if (int err = cubeb_stream_init(m_ctx, &m_stream, "Main stream", nullptr, nullptr, nullptr, &stream_param, stream_latency, data_cb, state_cb, this))
{
m_stream = nullptr;
Cubeb.error("cubeb_stream_init() failed: 0x%08x", err);
@ -110,6 +112,11 @@ void CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChanne
{
Cubeb.error("cubeb_stream_set_volume() failed: 0x%08x", err);
}
if (int err = cubeb_stream_start(m_stream))
{
Cubeb.error("cubeb_stream_start() failed: 0x%08x", err);
}
}
void CubebBackend::CloseUnlocked()
@ -125,7 +132,7 @@ void CubebBackend::CloseUnlocked()
m_playing = false;
m_stream = nullptr;
memset(m_last_sample, 0, sizeof(m_last_sample));
m_last_sample.fill(0);
}
void CubebBackend::Close()
@ -138,24 +145,15 @@ void CubebBackend::Play()
{
if (m_playing) return;
if (int err = cubeb_stream_start(m_stream))
{
Cubeb.error("cubeb_stream_start() failed: 0x%08x", err);
}
std::lock_guard lock(m_cb_mutex);
m_playing = true;
}
void CubebBackend::Pause()
{
if (int err = cubeb_stream_stop(m_stream))
{
Cubeb.error("cubeb_stream_stop() failed: 0x%08x", err);
}
std::lock_guard lock(m_cb_mutex);
m_playing = false;
m_last_sample.fill(0);
}
bool CubebBackend::IsPlaying()
@ -171,18 +169,13 @@ void CubebBackend::SetWriteCallback(std::function<u32(u32, void *)> cb)
f64 CubebBackend::GetCallbackFrameLen()
{
cubeb_stream_params stream_param{};
stream_param.format = get_convert_to_s16() ? CUBEB_SAMPLE_S16NE : CUBEB_SAMPLE_FLOAT32NE;
stream_param.rate = get_sampling_rate();
stream_param.channels = get_channels();
u32 min_latency{};
if (int err = cubeb_get_min_latency(m_ctx, &stream_param, &min_latency))
u32 stream_latency{};
if (int err = cubeb_stream_get_latency(m_stream, &stream_latency))
{
Cubeb.error("cubeb_get_min_latency() failed: 0x%08x", err);
Cubeb.error("cubeb_stream_get_latency() failed: 0x%08x", err);
}
return static_cast<f64>(std::max<u32>(AUDIO_MIN_LATENCY, min_latency)) / get_sampling_rate();
return std::max<f64>(AUDIO_MIN_LATENCY, static_cast<f64>(stream_latency) / get_sampling_rate());
}
long CubebBackend::data_cb(cubeb_stream* /* stream */, void* user_ptr, void const* /* input_buffer */, void* output_buffer, long nframes)
@ -192,21 +185,27 @@ long CubebBackend::data_cb(cubeb_stream* /* stream */, void* user_ptr, void cons
if (nframes && lock.try_lock() && cubeb->m_write_callback && cubeb->m_playing)
{
const u32 sample_size = cubeb->get_sample_size() * cubeb->get_channels();
const u32 bytes_req = nframes * cubeb->get_sample_size() * cubeb->get_channels();
const u32 sample_size = cubeb->full_sample_size.observe();
const u32 bytes_req = nframes * sample_size;
u32 written = std::min(cubeb->m_write_callback(bytes_req, output_buffer), bytes_req);
written -= written % sample_size;
if (written >= sample_size)
{
memcpy(cubeb->m_last_sample, static_cast<u8*>(output_buffer) + written - sample_size, sample_size);
memcpy(cubeb->m_last_sample.data(), static_cast<u8*>(output_buffer) + written - sample_size, sample_size);
}
for (u32 i = written; i < bytes_req; i += sample_size)
{
memcpy(static_cast<u8*>(output_buffer) + i, cubeb->m_last_sample, sample_size);
memcpy(static_cast<u8*>(output_buffer) + i, cubeb->m_last_sample.data(), sample_size);
}
}
else
{
// Stream parameters are modified only after stream_destroy. stream_destroy will return
// only after this callback returns, so it's safe to access full_sample_size here.
memset(output_buffer, 0, nframes * cubeb->full_sample_size.observe());
}
return nframes;
}

View File

@ -32,6 +32,8 @@ public:
bool IsPlaying() override;
private:
static constexpr f64 AUDIO_MIN_LATENCY = 512.0 / 48000; // 10ms
cubeb* m_ctx = nullptr;
cubeb_stream* m_stream = nullptr;
#ifdef _WIN32
@ -40,7 +42,8 @@ private:
shared_mutex m_cb_mutex{};
std::function<u32(u32, void *)> m_write_callback{};
u8 m_last_sample[sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)]{};
std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{};
atomic_t<u8> full_sample_size = 0;
bool m_playing = false;
atomic_t<bool> m_reset_req = false;

View File

@ -102,6 +102,7 @@ void FAudioBackend::Pause()
std::lock_guard lock(m_cb_mutex);
m_playing = false;
m_last_sample.fill(0);
}
void FAudioBackend::CloseUnlocked()
@ -119,7 +120,7 @@ void FAudioBackend::CloseUnlocked()
m_source_voice = nullptr;
m_data_buf = nullptr;
m_data_buf_len = 0;
memset(m_last_sample, 0, sizeof(m_last_sample));
m_last_sample.fill(0);
}
void FAudioBackend::Close()
@ -233,12 +234,12 @@ void FAudioBackend::OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj,
if (written >= sample_size)
{
memcpy(faudio->m_last_sample, faudio->m_data_buf.get() + written - sample_size, sample_size);
memcpy(faudio->m_last_sample.data(), faudio->m_data_buf.get() + written - sample_size, sample_size);
}
for (u32 i = written; i < BytesRequired; i += sample_size)
{
memcpy(faudio->m_data_buf.get() + i, faudio->m_last_sample, sample_size);
memcpy(faudio->m_data_buf.get() + i, faudio->m_last_sample.data(), sample_size);
}
FAudioBuffer buffer{};

View File

@ -45,7 +45,7 @@ private:
std::function<u32(u32, void *)> m_write_callback{};
std::unique_ptr<u8[]> m_data_buf{};
u64 m_data_buf_len = 0;
u8 m_last_sample[sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)]{};
std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{};
bool m_playing = false;
atomic_t<bool> m_reset_req = false;

View File

@ -124,7 +124,7 @@ void XAudio2Backend::CloseUnlocked()
m_playing = false;
m_data_buf = nullptr;
m_data_buf_len = 0;
memset(m_last_sample, 0, sizeof(m_last_sample));
m_last_sample.fill(0);
}
void XAudio2Backend::Close()
@ -156,6 +156,7 @@ void XAudio2Backend::Pause()
std::lock_guard lock(m_cb_mutex);
m_playing = false;
m_last_sample.fill(0);
}
void XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
@ -261,12 +262,12 @@ void XAudio2Backend::OnVoiceProcessingPassStart(UINT32 BytesRequired)
if (written >= sample_size)
{
memcpy(m_last_sample, m_data_buf.get() + written - sample_size, sample_size);
memcpy(m_last_sample.data(), m_data_buf.get() + written - sample_size, sample_size);
}
for (u32 i = written; i < BytesRequired; i += sample_size)
{
memcpy(m_data_buf.get() + i, m_last_sample, sample_size);
memcpy(m_data_buf.get() + i, m_last_sample.data(), sample_size);
}
XAUDIO2_BUFFER buffer{};

View File

@ -48,7 +48,7 @@ private:
std::function<u32(u32, void *)> m_write_callback{};
std::unique_ptr<u8[]> m_data_buf{};
u64 m_data_buf_len = 0;
u8 m_last_sample[sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)]{};
std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{};
bool m_playing = false;
atomic_t<bool> m_reset_req = false;