mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 02:32:36 +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:
parent
681bab558b
commit
7977fbb9c5
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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{};
|
||||
|
@ -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;
|
||||
|
@ -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{};
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user