mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-06 15:01:28 +12: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:
parent
681bab558b
commit
7977fbb9c5
7 changed files with 38 additions and 35 deletions
|
@ -8,7 +8,6 @@ enum : u32
|
||||||
DEFAULT_AUDIO_SAMPLING_RATE = 48000,
|
DEFAULT_AUDIO_SAMPLING_RATE = 48000,
|
||||||
MAX_AUDIO_BUFFERS = 64,
|
MAX_AUDIO_BUFFERS = 64,
|
||||||
AUDIO_BUFFER_SAMPLES = 256,
|
AUDIO_BUFFER_SAMPLES = 256,
|
||||||
AUDIO_MIN_LATENCY = 512,
|
|
||||||
AUDIO_MAX_CHANNELS = 8,
|
AUDIO_MAX_CHANNELS = 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@ void CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChanne
|
||||||
m_sampling_rate = freq;
|
m_sampling_rate = freq;
|
||||||
m_sample_size = sample_size;
|
m_sample_size = sample_size;
|
||||||
m_channels = ch_cnt;
|
m_channels = ch_cnt;
|
||||||
|
full_sample_size = get_channels() * get_sample_size();
|
||||||
|
|
||||||
cubeb_stream_params stream_param{};
|
cubeb_stream_params stream_param{};
|
||||||
stream_param.format = get_convert_to_s16() ? CUBEB_SAMPLE_S16NE : CUBEB_SAMPLE_FLOAT32NE;
|
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);
|
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;
|
m_stream = nullptr;
|
||||||
Cubeb.error("cubeb_stream_init() failed: 0x%08x", err);
|
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);
|
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()
|
void CubebBackend::CloseUnlocked()
|
||||||
|
@ -125,7 +132,7 @@ void CubebBackend::CloseUnlocked()
|
||||||
|
|
||||||
m_playing = false;
|
m_playing = false;
|
||||||
m_stream = nullptr;
|
m_stream = nullptr;
|
||||||
memset(m_last_sample, 0, sizeof(m_last_sample));
|
m_last_sample.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CubebBackend::Close()
|
void CubebBackend::Close()
|
||||||
|
@ -138,24 +145,15 @@ void CubebBackend::Play()
|
||||||
{
|
{
|
||||||
if (m_playing) return;
|
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);
|
std::lock_guard lock(m_cb_mutex);
|
||||||
m_playing = true;
|
m_playing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CubebBackend::Pause()
|
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);
|
std::lock_guard lock(m_cb_mutex);
|
||||||
m_playing = false;
|
m_playing = false;
|
||||||
|
m_last_sample.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CubebBackend::IsPlaying()
|
bool CubebBackend::IsPlaying()
|
||||||
|
@ -171,18 +169,13 @@ void CubebBackend::SetWriteCallback(std::function<u32(u32, void *)> cb)
|
||||||
|
|
||||||
f64 CubebBackend::GetCallbackFrameLen()
|
f64 CubebBackend::GetCallbackFrameLen()
|
||||||
{
|
{
|
||||||
cubeb_stream_params stream_param{};
|
u32 stream_latency{};
|
||||||
stream_param.format = get_convert_to_s16() ? CUBEB_SAMPLE_S16NE : CUBEB_SAMPLE_FLOAT32NE;
|
if (int err = cubeb_stream_get_latency(m_stream, &stream_latency))
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
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)
|
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)
|
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 sample_size = cubeb->full_sample_size.observe();
|
||||||
const u32 bytes_req = nframes * cubeb->get_sample_size() * cubeb->get_channels();
|
const u32 bytes_req = nframes * sample_size;
|
||||||
u32 written = std::min(cubeb->m_write_callback(bytes_req, output_buffer), bytes_req);
|
u32 written = std::min(cubeb->m_write_callback(bytes_req, output_buffer), bytes_req);
|
||||||
written -= written % sample_size;
|
written -= written % sample_size;
|
||||||
|
|
||||||
if (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)
|
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;
|
return nframes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ public:
|
||||||
bool IsPlaying() override;
|
bool IsPlaying() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr f64 AUDIO_MIN_LATENCY = 512.0 / 48000; // 10ms
|
||||||
|
|
||||||
cubeb* m_ctx = nullptr;
|
cubeb* m_ctx = nullptr;
|
||||||
cubeb_stream* m_stream = nullptr;
|
cubeb_stream* m_stream = nullptr;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -40,7 +42,8 @@ private:
|
||||||
|
|
||||||
shared_mutex m_cb_mutex{};
|
shared_mutex m_cb_mutex{};
|
||||||
std::function<u32(u32, void *)> m_write_callback{};
|
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;
|
bool m_playing = false;
|
||||||
atomic_t<bool> m_reset_req = false;
|
atomic_t<bool> m_reset_req = false;
|
||||||
|
|
|
@ -102,6 +102,7 @@ void FAudioBackend::Pause()
|
||||||
|
|
||||||
std::lock_guard lock(m_cb_mutex);
|
std::lock_guard lock(m_cb_mutex);
|
||||||
m_playing = false;
|
m_playing = false;
|
||||||
|
m_last_sample.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FAudioBackend::CloseUnlocked()
|
void FAudioBackend::CloseUnlocked()
|
||||||
|
@ -119,7 +120,7 @@ void FAudioBackend::CloseUnlocked()
|
||||||
m_source_voice = nullptr;
|
m_source_voice = nullptr;
|
||||||
m_data_buf = nullptr;
|
m_data_buf = nullptr;
|
||||||
m_data_buf_len = 0;
|
m_data_buf_len = 0;
|
||||||
memset(m_last_sample, 0, sizeof(m_last_sample));
|
m_last_sample.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FAudioBackend::Close()
|
void FAudioBackend::Close()
|
||||||
|
@ -233,12 +234,12 @@ void FAudioBackend::OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj,
|
||||||
|
|
||||||
if (written >= sample_size)
|
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)
|
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{};
|
FAudioBuffer buffer{};
|
||||||
|
|
|
@ -45,7 +45,7 @@ private:
|
||||||
std::function<u32(u32, void *)> m_write_callback{};
|
std::function<u32(u32, void *)> m_write_callback{};
|
||||||
std::unique_ptr<u8[]> m_data_buf{};
|
std::unique_ptr<u8[]> m_data_buf{};
|
||||||
u64 m_data_buf_len = 0;
|
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;
|
bool m_playing = false;
|
||||||
atomic_t<bool> m_reset_req = false;
|
atomic_t<bool> m_reset_req = false;
|
||||||
|
|
|
@ -124,7 +124,7 @@ void XAudio2Backend::CloseUnlocked()
|
||||||
m_playing = false;
|
m_playing = false;
|
||||||
m_data_buf = nullptr;
|
m_data_buf = nullptr;
|
||||||
m_data_buf_len = 0;
|
m_data_buf_len = 0;
|
||||||
memset(m_last_sample, 0, sizeof(m_last_sample));
|
m_last_sample.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void XAudio2Backend::Close()
|
void XAudio2Backend::Close()
|
||||||
|
@ -156,6 +156,7 @@ void XAudio2Backend::Pause()
|
||||||
|
|
||||||
std::lock_guard lock(m_cb_mutex);
|
std::lock_guard lock(m_cb_mutex);
|
||||||
m_playing = false;
|
m_playing = false;
|
||||||
|
m_last_sample.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
|
void XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
|
||||||
|
@ -261,12 +262,12 @@ void XAudio2Backend::OnVoiceProcessingPassStart(UINT32 BytesRequired)
|
||||||
|
|
||||||
if (written >= sample_size)
|
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)
|
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{};
|
XAUDIO2_BUFFER buffer{};
|
||||||
|
|
|
@ -48,7 +48,7 @@ private:
|
||||||
std::function<u32(u32, void *)> m_write_callback{};
|
std::function<u32(u32, void *)> m_write_callback{};
|
||||||
std::unique_ptr<u8[]> m_data_buf{};
|
std::unique_ptr<u8[]> m_data_buf{};
|
||||||
u64 m_data_buf_len = 0;
|
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;
|
bool m_playing = false;
|
||||||
atomic_t<bool> m_reset_req = false;
|
atomic_t<bool> m_reset_req = false;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue