From 531c3ad5faf7180929d93b1186a25f7d26b88415 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 6 Jun 2020 14:40:20 +1000 Subject: [PATCH] AudioStream: Replace buffer queue with ring buffer Should achieve a decent overall minimum latency reduction. --- src/common/audio_stream.cpp | 209 +++++++++------------ src/common/audio_stream.h | 61 +++--- src/common/cubeb_audio_stream.cpp | 33 +++- src/common/cubeb_audio_stream.h | 6 +- src/common/fifo_queue.h | 13 +- src/common/null_audio_stream.cpp | 4 +- src/common/null_audio_stream.h | 2 +- src/core/host_interface.cpp | 14 +- src/core/host_interface.h | 3 +- src/core/settings.cpp | 2 - src/core/settings.h | 1 - src/core/spu.cpp | 2 +- src/duckstation-qt/audiosettingswidget.cpp | 13 +- src/duckstation-qt/audiosettingswidget.ui | 39 +--- src/frontend-common/sdl_audio_stream.cpp | 46 +++-- src/frontend-common/sdl_audio_stream.h | 6 +- 16 files changed, 203 insertions(+), 251 deletions(-) diff --git a/src/common/audio_stream.cpp b/src/common/audio_stream.cpp index e415ddacb..f85d81a1f 100644 --- a/src/common/audio_stream.cpp +++ b/src/common/audio_stream.cpp @@ -1,14 +1,16 @@ #include "audio_stream.h" #include "assert.h" +#include "log.h" #include #include +Log_SetChannel(AudioStream); AudioStream::AudioStream() = default; AudioStream::~AudioStream() = default; bool AudioStream::Reconfigure(u32 output_sample_rate /*= DefaultOutputSampleRate*/, u32 channels /*= 1*/, - u32 buffer_size /*= DefaultBufferSize*/, u32 buffer_count /*= DefaultBufferCount*/) + u32 buffer_size /*= DefaultBufferSize*/) { if (IsDeviceOpen()) CloseDevice(); @@ -16,13 +18,14 @@ bool AudioStream::Reconfigure(u32 output_sample_rate /*= DefaultOutputSampleRate m_output_sample_rate = output_sample_rate; m_channels = channels; m_buffer_size = buffer_size; - AllocateBuffers(buffer_count); m_output_paused = true; + if (!SetBufferSize(buffer_size)) + return false; + if (!OpenDevice()) { EmptyBuffers(); - m_buffers.clear(); m_buffer_size = 0; m_output_sample_rate = 0; m_channels = 0; @@ -32,7 +35,7 @@ bool AudioStream::Reconfigure(u32 output_sample_rate /*= DefaultOutputSampleRate return true; } -void AudioStream::SetOutputVolume(s32 volume) +void AudioStream::SetOutputVolume(u32 volume) { std::unique_lock lock(m_buffer_mutex); m_output_volume = volume; @@ -58,7 +61,6 @@ void AudioStream::Shutdown() CloseDevice(); EmptyBuffers(); - m_buffers.clear(); m_buffer_size = 0; m_output_sample_rate = 0; m_channels = 0; @@ -69,182 +71,149 @@ void AudioStream::BeginWrite(SampleType** buffer_ptr, u32* num_frames) { m_buffer_mutex.lock(); - EnsureBuffer(); + EnsureBuffer(*num_frames * m_channels); - Buffer& buffer = m_buffers[m_first_free_buffer]; - *buffer_ptr = buffer.data.data() + (buffer.write_position * m_channels); - *num_frames = m_buffer_size - buffer.write_position; + *buffer_ptr = m_buffer.GetWritePointer(); + *num_frames = m_buffer.GetContiguousSpace() / m_channels; } void AudioStream::WriteFrames(const SampleType* frames, u32 num_frames) { - u32 remaining_frames = num_frames; + const u32 num_samples = num_frames * m_channels; std::unique_lock lock(m_buffer_mutex); - while (remaining_frames > 0) - { - EnsureBuffer(); - - Buffer& buffer = m_buffers[m_first_free_buffer]; - const u32 to_this_buffer = std::min(m_buffer_size - buffer.write_position, remaining_frames); - - const u32 copy_count = to_this_buffer * m_channels; - std::memcpy(&buffer.data[buffer.write_position * m_channels], frames, copy_count * sizeof(SampleType)); - frames += copy_count; - - remaining_frames -= to_this_buffer; - buffer.write_position += to_this_buffer; - - // End of the buffer? - if (buffer.write_position == m_buffer_size) - { - // Reset it back to the start, and enqueue it. - buffer.write_position = 0; - m_num_free_buffers--; - m_first_free_buffer = (m_first_free_buffer + 1) % m_buffers.size(); - m_num_available_buffers++; - BufferAvailable(); - } - } + EnsureBuffer(num_samples); + m_buffer.PushRange(frames, num_samples); + FramesAvailable(); } void AudioStream::EndWrite(u32 num_frames) { - Buffer& buffer = m_buffers[m_first_free_buffer]; - DebugAssert((buffer.write_position + num_frames) <= m_buffer_size); - buffer.write_position += num_frames; - - // End of the buffer? - if (buffer.write_position == m_buffer_size) - { - // Reset it back to the start, and enqueue it. - // Log_DevPrintf("Enqueue buffer %u", m_first_free_buffer); - buffer.write_position = 0; - m_num_free_buffers--; - m_first_free_buffer = (m_first_free_buffer + 1) % m_buffers.size(); - m_num_available_buffers++; - BufferAvailable(); - } + m_buffer.AdvanceTail(num_frames * m_channels); + FramesAvailable(); m_buffer_mutex.unlock(); } -float AudioStream::GetMinLatency(u32 sample_rate, u32 buffer_size, u32 buffer_count) +float AudioStream::GetMaxLatency(u32 sample_rate, u32 buffer_size) { return (static_cast(buffer_size) / static_cast(sample_rate)); } -float AudioStream::GetMaxLatency(u32 sample_rate, u32 buffer_size, u32 buffer_count) +bool AudioStream::SetBufferSize(u32 buffer_size) { - return (static_cast(buffer_size * (buffer_count - 1)) / static_cast(sample_rate)); + const u32 buffer_size_in_samples = buffer_size * m_channels; + const u32 max_samples = buffer_size_in_samples * 2u; + if (max_samples > m_buffer.GetCapacity()) + return false; + + m_buffer_size = buffer_size; + m_max_samples = max_samples; + return true; } u32 AudioStream::GetSamplesAvailable() const { // TODO: Use atomic loads - u32 available_buffers; + u32 available_samples; { std::unique_lock lock(m_buffer_mutex); - available_buffers = m_num_available_buffers; + available_samples = m_buffer.GetSize(); } - return available_buffers * m_buffer_size; + return available_samples / m_channels; } -u32 AudioStream::ReadSamples(SampleType* samples, u32 num_samples) +u32 AudioStream::GetSamplesAvailableLocked() const { - u32 remaining_samples = num_samples; - std::unique_lock lock(m_buffer_mutex); + return m_buffer.GetSize() / m_channels; +} - while (remaining_samples > 0 && m_num_available_buffers > 0) +void AudioStream::ReadFrames(SampleType* samples, u32 num_frames, bool apply_volume) +{ + const u32 total_samples = num_frames * m_channels; + u32 samples_copied = 0; { - Buffer& buffer = m_buffers[m_first_available_buffer]; - const u32 from_this_buffer = std::min(m_buffer_size - buffer.read_position, remaining_samples); - - const u32 copy_count = from_this_buffer * m_channels; - const SampleType* read_pointer = &buffer.data[buffer.read_position * m_channels]; - for (u32 i = 0; i < copy_count; i++) - *(samples++) = ApplyVolume(*(read_pointer++), m_output_volume); + std::unique_lock lock(m_buffer_mutex); + samples_copied = std::min(m_buffer.GetSize(), total_samples); + if (samples_copied > 0) + m_buffer.PopRange(samples, samples_copied); - remaining_samples -= from_this_buffer; - buffer.read_position += from_this_buffer; + m_buffer_draining_cv.notify_one(); + } - if (buffer.read_position == m_buffer_size) + if (samples_copied < total_samples) + { + if (samples_copied > 0) { - // Log_DevPrintf("Finish dequeing buffer %u", m_first_available_buffer); - // End of this buffer. - buffer.read_position = 0; - m_num_available_buffers--; - m_first_available_buffer = (m_first_available_buffer + 1) % m_buffers.size(); - m_num_free_buffers++; - m_buffer_available_cv.notify_one(); + m_resample_buffer.resize(samples_copied); + std::memcpy(m_resample_buffer.data(), samples, sizeof(SampleType) * samples_copied); + + // super basic resampler - spread the input samples evenly across the output samples. will sound like ass and have + // aliasing, but better than popping by inserting silence. + const u32 increment = + static_cast(65536.0f * (static_cast(samples_copied / m_channels) / static_cast(num_frames))); + + SampleType* out_ptr = samples; + const SampleType* resample_ptr = m_resample_buffer.data(); + const u32 copy_stride = sizeof(SampleType) * m_channels; + u32 resample_subpos = 0; + for (u32 i = 0; i < num_frames; i++) + { + std::memcpy(out_ptr, resample_ptr, copy_stride); + out_ptr += m_channels; + + resample_subpos += increment; + resample_ptr += (resample_subpos >> 16) * m_channels; + resample_subpos %= 65536u; + } + + Log_DevPrintf("Audio buffer underflow, resampled %u frames to %u", samples_copied / m_channels, num_frames); + } + else + { + // read nothing, so zero-fill + std::memset(samples, 0, sizeof(SampleType) * total_samples); + Log_DevPrintf("Audio buffer underflow with no samples, added %u frames silence", num_frames); } } - return num_samples - remaining_samples; -} - -void AudioStream::AllocateBuffers(u32 buffer_count) -{ - m_buffers.resize(buffer_count); - for (u32 i = 0; i < buffer_count; i++) + if (apply_volume && m_output_volume != FullVolume) { - Buffer& buffer = m_buffers[i]; - buffer.data.resize(m_buffer_size * m_channels); - buffer.read_position = 0; - buffer.write_position = 0; + SampleType* current_ptr = samples; + const SampleType* end_ptr = samples + (num_frames * m_channels); + while (current_ptr != end_ptr) + { + *current_ptr = ApplyVolume(*current_ptr, m_output_volume); + current_ptr++; + } } - - m_first_available_buffer = 0; - m_num_available_buffers = 0; - m_first_free_buffer = 0; - m_num_free_buffers = buffer_count; } -void AudioStream::EnsureBuffer() +void AudioStream::EnsureBuffer(u32 size) { - if (m_num_free_buffers > 0) + if (GetBufferSpace() >= size) return; if (m_sync) { std::unique_lock lock(m_buffer_mutex, std::adopt_lock); - m_buffer_available_cv.wait(lock, [this]() { return m_num_free_buffers > 0; }); + m_buffer_draining_cv.wait(lock, [this, size]() { return GetBufferSpace() >= size; }); lock.release(); } else { - DropBuffer(); + m_buffer.Remove(size); } } -void AudioStream::DropBuffer() +void AudioStream::DropFrames(u32 count) { - DebugAssert(m_num_available_buffers > 0); - // Log_DevPrintf("Dropping buffer %u", m_first_free_buffer); - - // Out of space. We'll overwrite the oldest buffer with the new data. - // At the same time, we shift the available buffer forward one. - m_first_available_buffer = (m_first_available_buffer + 1) % m_buffers.size(); - m_num_available_buffers--; - - m_buffers[m_first_free_buffer].read_position = 0; - m_buffers[m_first_free_buffer].write_position = 0; - m_num_free_buffers++; + m_buffer.Remove(count); } void AudioStream::EmptyBuffers() { std::unique_lock lock(m_buffer_mutex); - - for (Buffer& buffer : m_buffers) - { - buffer.read_position = 0; - buffer.write_position = 0; - } - - m_first_free_buffer = 0; - m_num_free_buffers = static_cast(m_buffers.size()); - m_first_available_buffer = 0; - m_num_available_buffers = 0; + m_buffer.Clear(); } diff --git a/src/common/audio_stream.h b/src/common/audio_stream.h index 0e00c2a3b..898fa8558 100644 --- a/src/common/audio_stream.h +++ b/src/common/audio_stream.h @@ -1,4 +1,5 @@ #pragma once +#include "fifo_queue.h" #include "types.h" #include #include @@ -12,11 +13,12 @@ class AudioStream public: using SampleType = s16; - enum + enum : u32 { DefaultOutputSampleRate = 44100, DefaultBufferSize = 2048, - DefaultBufferCount = 3, + MaxSamples = 32768, + FullVolume = 100 }; AudioStream(); @@ -25,14 +27,14 @@ public: u32 GetOutputSampleRate() const { return m_output_sample_rate; } u32 GetChannels() const { return m_channels; } u32 GetBufferSize() const { return m_buffer_size; } - u32 GetBufferCount() const { return static_cast(m_buffers.size()); } s32 GetOutputVolume() const { return m_output_volume; } bool IsSyncing() const { return m_sync; } bool Reconfigure(u32 output_sample_rate = DefaultOutputSampleRate, u32 channels = 1, - u32 buffer_size = DefaultBufferSize, u32 buffer_count = DefaultBufferCount); + u32 buffer_size = DefaultBufferSize); void SetSync(bool enable) { m_sync = enable; } - void SetOutputVolume(s32 volume); + + virtual void SetOutputVolume(u32 volume); void PauseOutput(bool paused); void EmptyBuffers(); @@ -48,58 +50,43 @@ public: static std::unique_ptr CreateCubebAudioStream(); // Latency computation - returns values in seconds - static float GetMinLatency(u32 sample_rate, u32 buffer_size, u32 buffer_count); - static float GetMaxLatency(u32 sample_rate, u32 buffer_size, u32 buffer_count); + static float GetMaxLatency(u32 sample_rate, u32 buffer_size); protected: virtual bool OpenDevice() = 0; virtual void PauseDevice(bool paused) = 0; virtual void CloseDevice() = 0; - virtual void BufferAvailable() = 0; + virtual void FramesAvailable() = 0; - ALWAYS_INLINE static SampleType ApplyVolume(SampleType sample, s32 volume) + ALWAYS_INLINE static SampleType ApplyVolume(SampleType sample, u32 volume) { - return s16((s32(sample) * volume) / 100); + return s16((s32(sample) * s32(volume)) / 100); } + bool SetBufferSize(u32 buffer_size); bool IsDeviceOpen() const { return (m_output_sample_rate > 0); } u32 GetSamplesAvailable() const; - u32 ReadSamples(SampleType* samples, u32 num_samples); - - void DropBuffer(); + u32 GetSamplesAvailableLocked() const; + void ReadFrames(SampleType* samples, u32 num_frames, bool apply_volume); + void DropFrames(u32 count); u32 m_output_sample_rate = 0; u32 m_channels = 0; u32 m_buffer_size = 0; -private: - struct Buffer - { - std::vector data; - u32 write_position; - u32 read_position; - }; + // volume, 0-100 + u32 m_output_volume = FullVolume; - void AllocateBuffers(u32 buffer_count); - void EnsureBuffer(); +private: + ALWAYS_INLINE u32 GetBufferSpace() const { return (m_max_samples - m_buffer.GetSize()); } + void EnsureBuffer(u32 size); - std::vector m_buffers; + HeapFIFOQueue m_buffer; mutable std::mutex m_buffer_mutex; - - // For input. - u32 m_first_free_buffer = 0; - u32 m_num_free_buffers = 0; - - // For output. - u32 m_num_available_buffers = 0; - u32 m_first_available_buffer = 0; - - // TODO: Switch to semaphore - std::condition_variable m_buffer_available_cv; - - // volume, 0-100 - s32 m_output_volume = 100; + std::condition_variable m_buffer_draining_cv; + std::vector m_resample_buffer; + u32 m_max_samples = 0; bool m_output_paused = true; bool m_sync = true; diff --git a/src/common/cubeb_audio_stream.cpp b/src/common/cubeb_audio_stream.cpp index 7888e0577..ccf1dc5ec 100644 --- a/src/common/cubeb_audio_stream.cpp +++ b/src/common/cubeb_audio_stream.cpp @@ -56,9 +56,21 @@ bool CubebAudioStream::OpenDevice() Log_InfoPrintf("Minimum latency in frames: %u", latency_frames); if (latency_frames > m_buffer_size) - Log_WarningPrintf("Minimum latency is above buffer size: %u vs %u", latency_frames, m_buffer_size); + { + Log_WarningPrintf("Minimum latency is above buffer size: %u vs %u, adjusting to compensate.", latency_frames, + m_buffer_size); + + if (!SetBufferSize(latency_frames)) + { + Log_ErrorPrintf("Failed to set new buffer size of %u frames", latency_frames); + DestroyContext(); + return false; + } + } else + { latency_frames = m_buffer_size; + } char stream_name[32]; std::snprintf(stream_name, sizeof(stream_name), "AudioStream_%p", this); @@ -72,6 +84,7 @@ bool CubebAudioStream::OpenDevice() return false; } + cubeb_stream_set_volume(m_cubeb_stream, static_cast(m_output_volume) / 100.0f); return true; } @@ -106,15 +119,13 @@ long CubebAudioStream::DataCallback(cubeb_stream* stm, void* user_ptr, const voi { CubebAudioStream* const this_ptr = static_cast(user_ptr); - const u32 read_frames = - this_ptr->ReadSamples(reinterpret_cast(output_buffer), static_cast(nframes)); - const u32 silence_frames = static_cast(nframes) - read_frames; - if (silence_frames > 0) + if (this_ptr->m_output_volume_changed.load()) { - std::memset(reinterpret_cast(output_buffer) + (read_frames * this_ptr->m_channels), 0, - silence_frames * this_ptr->m_channels * sizeof(SampleType)); + this_ptr->m_output_volume_changed.store(false); + cubeb_stream_set_volume(this_ptr->m_cubeb_stream, static_cast(this_ptr->m_output_volume) / 100.0f); } + this_ptr->ReadFrames(reinterpret_cast(output_buffer), static_cast(nframes), false); return nframes; } @@ -125,7 +136,7 @@ void CubebAudioStream::StateCallback(cubeb_stream* stream, void* user_ptr, cubeb this_ptr->m_paused = (state != CUBEB_STATE_STARTED); } -void CubebAudioStream::BufferAvailable() {} +void CubebAudioStream::FramesAvailable() {} void CubebAudioStream::DestroyContext() { @@ -138,6 +149,12 @@ void CubebAudioStream::DestroyContext() #endif } +void CubebAudioStream::SetOutputVolume(u32 volume) +{ + AudioStream::SetOutputVolume(volume); + m_output_volume_changed.store(true); +} + std::unique_ptr AudioStream::CreateCubebAudioStream() { return std::make_unique(); diff --git a/src/common/cubeb_audio_stream.h b/src/common/cubeb_audio_stream.h index 34863fd01..ea5e3cf90 100644 --- a/src/common/cubeb_audio_stream.h +++ b/src/common/cubeb_audio_stream.h @@ -1,6 +1,7 @@ #pragma once #include "common/audio_stream.h" #include "cubeb/cubeb.h" +#include #include class CubebAudioStream final : public AudioStream @@ -9,13 +10,15 @@ public: CubebAudioStream(); ~CubebAudioStream(); + void SetOutputVolume(u32 volume) override; + protected: bool IsOpen() const { return m_cubeb_stream != nullptr; } bool OpenDevice() override; void PauseDevice(bool paused) override; void CloseDevice() override; - void BufferAvailable() override; + void FramesAvailable() override; void DestroyContext(); @@ -26,6 +29,7 @@ protected: cubeb* m_cubeb_context = nullptr; cubeb_stream* m_cubeb_stream = nullptr; bool m_paused = true; + std::atomic_bool m_output_volume_changed{ false }; #ifdef WIN32 bool m_com_initialized_by_us = false; diff --git a/src/common/fifo_queue.h b/src/common/fifo_queue.h index b2781da3a..c4a67fa6b 100644 --- a/src/common/fifo_queue.h +++ b/src/common/fifo_queue.h @@ -17,9 +17,10 @@ class FIFOQueue public: const T* GetDataPointer() const { return m_ptr; } T* GetDataPointer() { return m_ptr; } - const T* GetFrontPointer() const { return &m_ptr[m_head]; } - T* GetFrontPointer() { return &m_ptr[m_head]; } + const T* GetReadPointer() const { return &m_ptr[m_head]; } + T* GetReadPointer() { return &m_ptr[m_head]; } constexpr u32 GetCapacity() const { return CAPACITY; } + T* GetWritePointer() { return &m_ptr[m_tail]; } u32 GetSize() const { return m_size; } u32 GetSpace() const { return CAPACITY - m_size; } u32 GetContiguousSpace() const { return (m_tail >= m_head) ? (CAPACITY - m_tail) : (m_head - m_tail); } @@ -148,6 +149,14 @@ public: } } + void AdvanceTail(u32 count) + { + DebugAssert((m_size + count) < CAPACITY); + DebugAssert((m_tail + count) <= CAPACITY); + m_tail = (m_tail + count) % CAPACITY; + m_size += count; + } + protected: FIFOQueue() = default; diff --git a/src/common/null_audio_stream.cpp b/src/common/null_audio_stream.cpp index f8f4662f2..a07fa5fdf 100644 --- a/src/common/null_audio_stream.cpp +++ b/src/common/null_audio_stream.cpp @@ -13,10 +13,10 @@ void NullAudioStream::PauseDevice(bool paused) {} void NullAudioStream::CloseDevice() {} -void NullAudioStream::BufferAvailable() +void NullAudioStream::FramesAvailable() { // drop any buffer as soon as they're available - DropBuffer(); + DropFrames(GetSamplesAvailableLocked()); } std::unique_ptr AudioStream::CreateNullAudioStream() diff --git a/src/common/null_audio_stream.h b/src/common/null_audio_stream.h index 40a6a243b..55ce9a3f8 100644 --- a/src/common/null_audio_stream.h +++ b/src/common/null_audio_stream.h @@ -11,5 +11,5 @@ protected: bool OpenDevice() override; void PauseDevice(bool paused) override; void CloseDevice() override; - void BufferAvailable() override; + void FramesAvailable() override; }; diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index cb55020b7..7f26059d8 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -61,20 +61,18 @@ void HostInterface::Shutdown() {} void HostInterface::CreateAudioStream() { - Log_InfoPrintf("Creating '%s' audio stream, sample rate = %u, channels = %u, buffer size = %u, buffer count = %u", + Log_InfoPrintf("Creating '%s' audio stream, sample rate = %u, channels = %u, buffer size = %u", Settings::GetAudioBackendName(m_settings.audio_backend), AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, - m_settings.audio_buffer_size, m_settings.audio_buffer_count); + m_settings.audio_buffer_size); m_audio_stream = CreateAudioStream(m_settings.audio_backend); - if (!m_audio_stream || !m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, m_settings.audio_buffer_size, - m_settings.audio_buffer_count)) + if (!m_audio_stream || !m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, m_settings.audio_buffer_size)) { ReportFormattedError("Failed to create or configure audio stream, falling back to null output."); m_audio_stream.reset(); m_audio_stream = AudioStream::CreateNullAudioStream(); - m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, m_settings.audio_buffer_size, - m_settings.audio_buffer_count); + m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, m_settings.audio_buffer_size); } m_audio_stream->SetOutputVolume(m_settings.audio_output_muted ? 0 : m_settings.audio_output_volume); @@ -1011,7 +1009,6 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetStringValue("Audio", "Backend", Settings::GetAudioBackendName(AudioBackend::Cubeb)); si.SetIntValue("Audio", "OutputVolume", 100); si.SetIntValue("Audio", "BufferSize", DEFAULT_AUDIO_BUFFER_SIZE); - si.SetIntValue("Audio", "BufferCount", DEFAULT_AUDIO_BUFFER_COUNT); si.SetIntValue("Audio", "OutputMuted", false); si.SetBoolValue("Audio", "Sync", true); si.SetBoolValue("Audio", "DumpOnBoot", false); @@ -1072,8 +1069,7 @@ void HostInterface::UpdateSettings(SettingsInterface& si) } if (m_settings.audio_backend != old_settings.audio_backend || - m_settings.audio_buffer_size != old_settings.audio_buffer_size || - m_settings.audio_buffer_count != old_settings.audio_buffer_count) + m_settings.audio_buffer_size != old_settings.audio_buffer_size) { if (m_settings.audio_backend != old_settings.audio_backend) ReportFormattedMessage("Switching to %s audio backend.", diff --git a/src/core/host_interface.h b/src/core/host_interface.h index 498b83121..0871f8c55 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -38,8 +38,7 @@ public: { AUDIO_SAMPLE_RATE = 44100, AUDIO_CHANNELS = 2, - DEFAULT_AUDIO_BUFFER_SIZE = 2048, - DEFAULT_AUDIO_BUFFER_COUNT = 4 + DEFAULT_AUDIO_BUFFER_SIZE = 2048 }; struct SaveStateInfo diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 04d7b71a2..0177ee413 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -61,7 +61,6 @@ void Settings::Load(SettingsInterface& si) ParseAudioBackend(si.GetStringValue("Audio", "Backend", "Cubeb").c_str()).value_or(AudioBackend::Cubeb); audio_output_volume = si.GetIntValue("Audio", "OutputVolume", 100); audio_buffer_size = si.GetIntValue("Audio", "BufferSize", HostInterface::DEFAULT_AUDIO_BUFFER_SIZE); - audio_buffer_count = si.GetIntValue("Audio", "BufferCount", HostInterface::DEFAULT_AUDIO_BUFFER_COUNT); audio_output_muted = si.GetBoolValue("Audio", "OutputMuted", false); audio_sync_enabled = si.GetBoolValue("Audio", "Sync", true); audio_dump_on_boot = si.GetBoolValue("Audio", "DumpOnBoot", false); @@ -151,7 +150,6 @@ void Settings::Save(SettingsInterface& si) const si.SetStringValue("Audio", "Backend", GetAudioBackendName(audio_backend)); si.SetIntValue("Audio", "OutputVolume", audio_output_volume); si.SetIntValue("Audio", "BufferSize", audio_buffer_size); - si.SetIntValue("Audio", "BufferCount", audio_buffer_count); si.SetBoolValue("Audio", "OutputMuted", audio_output_muted); si.SetBoolValue("Audio", "Sync", audio_sync_enabled); si.SetBoolValue("Audio", "DumpOnBoot", audio_dump_on_boot); diff --git a/src/core/settings.h b/src/core/settings.h index 8a26155a5..e7337b954 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -71,7 +71,6 @@ struct Settings AudioBackend audio_backend = AudioBackend::Cubeb; s32 audio_output_volume = 100; u32 audio_buffer_size = 2048; - u32 audio_buffer_count = 4; bool audio_output_muted = false; bool audio_sync_enabled = true; bool audio_dump_on_boot = true; diff --git a/src/core/spu.cpp b/src/core/spu.cpp index 8c8b1465f..e7e802bda 100644 --- a/src/core/spu.cpp +++ b/src/core/spu.cpp @@ -674,7 +674,7 @@ void SPU::Execute(TickCount ticks) { AudioStream* const output_stream = m_system->GetHostInterface()->GetAudioStream(); s16* output_frame_start; - u32 output_frame_space; + u32 output_frame_space = remaining_frames; output_stream->BeginWrite(&output_frame_start, &output_frame_space); s16* output_frame = output_frame_start; diff --git a/src/duckstation-qt/audiosettingswidget.cpp b/src/duckstation-qt/audiosettingswidget.cpp index 6e687ffb5..4fbd3b014 100644 --- a/src/duckstation-qt/audiosettingswidget.cpp +++ b/src/duckstation-qt/audiosettingswidget.cpp @@ -14,13 +14,11 @@ AudioSettingsWidget::AudioSettingsWidget(QtHostInterface* host_interface, QWidge &Settings::ParseAudioBackend, &Settings::GetAudioBackendName); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.syncToOutput, "Audio/Sync"); SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.bufferSize, "Audio/BufferSize"); - SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.bufferCount, "Audio/BufferCount"); SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.volume, "Audio/OutputVolume"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.muted, "Audio/OutputMuted"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.startDumpingOnBoot, "Audio/DumpOnBoot"); connect(m_ui.bufferSize, &QSlider::valueChanged, this, &AudioSettingsWidget::updateBufferingLabel); - connect(m_ui.bufferCount, &QSlider::valueChanged, this, &AudioSettingsWidget::updateBufferingLabel); connect(m_ui.volume, &QSlider::valueChanged, this, &AudioSettingsWidget::updateVolumeLabel); updateBufferingLabel(); @@ -32,14 +30,9 @@ AudioSettingsWidget::~AudioSettingsWidget() = default; void AudioSettingsWidget::updateBufferingLabel() { const u32 buffer_size = static_cast(m_ui.bufferSize->value()); - const u32 buffer_count = static_cast(m_ui.bufferCount->value()); - const float min_latency = AudioStream::GetMinLatency(HostInterface::AUDIO_SAMPLE_RATE, buffer_size, buffer_count); - const float max_latency = AudioStream::GetMaxLatency(HostInterface::AUDIO_SAMPLE_RATE, buffer_size, buffer_count); - m_ui.bufferingLabel->setText(tr("%1 samples, %2 buffers (min %3ms, max %4ms)") - .arg(buffer_size) - .arg(buffer_count) - .arg(min_latency * 1000.0f, 0, 'f', 2) - .arg(max_latency * 1000.0f, 0, 'f', 2)); + const float max_latency = AudioStream::GetMaxLatency(HostInterface::AUDIO_SAMPLE_RATE, buffer_size); + m_ui.bufferingLabel->setText( + tr("Maximum latency: %1 frames (%2ms)").arg(buffer_size).arg(max_latency * 1000.0f, 0, 'f', 2)); } void AudioSettingsWidget::updateVolumeLabel() diff --git a/src/duckstation-qt/audiosettingswidget.ui b/src/duckstation-qt/audiosettingswidget.ui index 28e5ba082..0052907a8 100644 --- a/src/duckstation-qt/audiosettingswidget.ui +++ b/src/duckstation-qt/audiosettingswidget.ui @@ -74,7 +74,7 @@ - + Qt::Horizontal @@ -87,59 +87,30 @@ - + - 2048 samples, 4 buffers (min 0.00ms, max 0.00ms)) + Maximum latency: 0 frames (0.00ms) Qt::AlignCenter - + Sync To Output - + Start Dumping On Boot - - - - Buffer Count: - - - - - - - 2 - - - 8 - - - 1 - - - Qt::Horizontal - - - QSlider::TicksBothSides - - - 1 - - - diff --git a/src/frontend-common/sdl_audio_stream.cpp b/src/frontend-common/sdl_audio_stream.cpp index f928de341..0bf4164f3 100644 --- a/src/frontend-common/sdl_audio_stream.cpp +++ b/src/frontend-common/sdl_audio_stream.cpp @@ -1,7 +1,7 @@ #include "sdl_audio_stream.h" -#include "sdl_initializer.h" #include "common/assert.h" #include "common/log.h" +#include "sdl_initializer.h" #include Log_SetChannel(SDLAudioStream); @@ -9,7 +9,7 @@ SDLAudioStream::SDLAudioStream() = default; SDLAudioStream::~SDLAudioStream() { - if (m_is_open) + if (IsOpen()) SDLAudioStream::CloseDevice(); } @@ -20,7 +20,7 @@ std::unique_ptr SDLAudioStream::Create() bool SDLAudioStream::OpenDevice() { - DebugAssert(!m_is_open); + DebugAssert(!IsOpen()); FrontendCommon::EnsureSDLInitialized(); @@ -38,41 +38,49 @@ bool SDLAudioStream::OpenDevice() spec.callback = AudioCallback; spec.userdata = static_cast(this); - if (SDL_OpenAudio(&spec, nullptr) < 0) + SDL_AudioSpec obtained_spec = {}; + m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained_spec, SDL_AUDIO_ALLOW_SAMPLES_CHANGE); + if (m_device_id == 0) { - Log_ErrorPrintf("SDL_OpenAudio failed"); + Log_ErrorPrintf("SDL_OpenAudioDevice() failed: %s", SDL_GetError()); SDL_QuitSubSystem(SDL_INIT_AUDIO); return false; } - m_is_open = true; + if (obtained_spec.samples > spec.samples) + { + Log_WarningPrintf("Requested buffer size %u, got buffer size %u. Adjusting to compensate.", spec.samples, + obtained_spec.samples); + + if (!SetBufferSize(obtained_spec.samples)) + { + Log_ErrorPrintf("Failed to set new buffer size of %u", obtained_spec.samples); + CloseDevice(); + return false; + } + } + return true; } void SDLAudioStream::PauseDevice(bool paused) { - SDL_PauseAudio(paused ? 1 : 0); + SDL_PauseAudioDevice(m_device_id, paused ? 1 : 0); } void SDLAudioStream::CloseDevice() { - DebugAssert(m_is_open); - SDL_CloseAudio(); + SDL_CloseAudioDevice(m_device_id); SDL_QuitSubSystem(SDL_INIT_AUDIO); - m_is_open = false; + m_device_id = 0; } void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len) { SDLAudioStream* const this_ptr = static_cast(userdata); - const u32 num_samples = len / sizeof(SampleType) / this_ptr->m_channels; - const u32 read_samples = this_ptr->ReadSamples(reinterpret_cast(stream), num_samples); - const u32 silence_samples = num_samples - read_samples; - if (silence_samples > 0) - { - std::memset(reinterpret_cast(stream) + (read_samples * this_ptr->m_channels), 0, - silence_samples * this_ptr->m_channels * sizeof(SampleType)); - } + const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_channels; + + this_ptr->ReadFrames(reinterpret_cast(stream), num_frames, false); } -void SDLAudioStream::BufferAvailable() {} +void SDLAudioStream::FramesAvailable() {} diff --git a/src/frontend-common/sdl_audio_stream.h b/src/frontend-common/sdl_audio_stream.h index b33a59eff..184b942fe 100644 --- a/src/frontend-common/sdl_audio_stream.h +++ b/src/frontend-common/sdl_audio_stream.h @@ -11,12 +11,14 @@ public: static std::unique_ptr Create(); protected: + ALWAYS_INLINE bool IsOpen() const { return (m_device_id != 0); } + bool OpenDevice() override; void PauseDevice(bool paused) override; void CloseDevice() override; - void BufferAvailable() override; + void FramesAvailable() override; static void AudioCallback(void* userdata, uint8_t* stream, int len); - bool m_is_open = false; + u32 m_device_id = 0; };