diff --git a/src/common/audio_stream.cpp b/src/common/audio_stream.cpp new file mode 100644 index 000000000..1a2abff0a --- /dev/null +++ b/src/common/audio_stream.cpp @@ -0,0 +1,201 @@ +#include "audio_stream.h" +#include "YBaseLib/Assert.h" +#include "YBaseLib/Log.h" +Log_SetChannel(Audio); + +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*/) +{ + if (IsDeviceOpen()) + CloseDevice(); + + m_output_sample_rate = output_sample_rate; + m_channels = channels; + m_buffer_size = buffer_size; + AllocateBuffers(buffer_count); + m_output_paused = true; + + if (!OpenDevice()) + { + EmptyBuffers(); + m_buffers.clear(); + m_buffer_size = 0; + m_output_sample_rate = 0; + m_channels = 0; + return false; + } + + return true; +} + +void AudioStream::PauseOutput(bool paused) +{ + if (m_output_paused == paused) + return; + + PauseDevice(paused); + m_output_paused = paused; + + // Empty buffers on pause. + if (paused) + EmptyBuffers(); +} + +void AudioStream::Shutdown() +{ + if (!IsDeviceOpen()) + return; + + CloseDevice(); + EmptyBuffers(); + m_buffers.clear(); + m_buffer_size = 0; + m_output_sample_rate = 0; + m_channels = 0; + m_output_paused = true; +} + +void AudioStream::BeginWrite(SampleType** buffer_ptr, u32* num_samples) +{ + m_buffer_lock.lock(); + + if (m_num_free_buffers == 0) + DropBuffer(); + + Buffer& buffer = m_buffers[m_first_free_buffer]; + *buffer_ptr = buffer.data.data() + buffer.write_position; + *num_samples = m_buffer_size - buffer.write_position; +} + +void AudioStream::WriteSamples(const SampleType* samples, u32 num_samples) +{ + std::lock_guard guard(m_buffer_lock); + u32 remaining_samples = num_samples; + + while (remaining_samples > 0) + { + if (m_num_free_buffers == 0) + DropBuffer(); + + Buffer& buffer = m_buffers[m_first_free_buffer]; + const u32 to_this_buffer = std::min(m_buffer_size - buffer.write_position, remaining_samples); + + const u32 copy_count = to_this_buffer * m_channels; + std::memcpy(&buffer.data[buffer.write_position * m_channels], samples, copy_count * sizeof(SampleType)); + samples += copy_count; + + remaining_samples -= 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++; + } + } +} + +void AudioStream::EndWrite(u32 num_samples) +{ + Buffer& buffer = m_buffers[m_first_free_buffer]; + DebugAssert((buffer.write_position + num_samples) <= m_buffer_size); + buffer.write_position += num_samples; + + // 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++; + } + + m_buffer_lock.unlock(); +} + +u32 AudioStream::ReadSamples(SampleType* samples, u32 num_samples) +{ + std::lock_guard guard(m_buffer_lock); + u32 remaining_samples = num_samples; + + while (remaining_samples > 0 && m_num_available_buffers > 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; + std::memcpy(samples, &buffer.data[buffer.read_position * m_channels], copy_count * sizeof(SampleType)); + samples += copy_count; + + remaining_samples -= from_this_buffer; + buffer.read_position += from_this_buffer; + + if (buffer.read_position == m_buffer_size) + { + // 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++; + } + } + + return num_samples - remaining_samples; +} + +void AudioStream::AllocateBuffers(u32 buffer_count) +{ + m_buffers.resize(buffer_count); + for (u32 i = 0; i < buffer_count; i++) + { + Buffer& buffer = m_buffers[i]; + buffer.data.resize(m_buffer_size * m_channels); + buffer.read_position = 0; + buffer.write_position = 0; + } + + m_first_available_buffer = 0; + m_num_available_buffers = 0; + m_first_free_buffer = 0; + m_num_free_buffers = buffer_count; +} + +void AudioStream::DropBuffer() +{ + 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++; +} + +void AudioStream::EmptyBuffers() +{ + 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; +} diff --git a/src/common/audio_stream.h b/src/common/audio_stream.h new file mode 100644 index 000000000..c502d6b28 --- /dev/null +++ b/src/common/audio_stream.h @@ -0,0 +1,77 @@ +#pragma once +#include "types.h" +#include +#include +#include + +// Uses signed 16-bits samples. + +class AudioStream +{ +public: + using SampleType = s16; + + enum + { + DefaultOutputSampleRate = 44100, + DefaultBufferSize = 2048, + DefaultBufferCount = 3, + }; + + AudioStream(); + virtual ~AudioStream(); + + 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()); } + + bool Reconfigure(u32 output_sample_rate = DefaultOutputSampleRate, u32 channels = 1, + u32 buffer_size = DefaultBufferSize, u32 buffer_count = DefaultBufferCount); + + void PauseOutput(bool paused); + void EmptyBuffers(); + + void Shutdown(); + + void BeginWrite(SampleType** buffer_ptr, u32* num_samples); + void WriteSamples(const SampleType* samples, u32 num_samples); + void EndWrite(u32 num_samples); + +protected: + virtual bool OpenDevice() = 0; + virtual void PauseDevice(bool paused) = 0; + virtual void CloseDevice() = 0; + + bool IsDeviceOpen() const { return (m_output_sample_rate > 0); } + + u32 ReadSamples(SampleType* samples, u32 num_samples); + + 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; + }; + + void AllocateBuffers(u32 buffer_count); + void DropBuffer(); + + std::vector m_buffers; + std::mutex m_buffer_lock; + + // 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; + + bool m_output_paused = true; +}; \ No newline at end of file diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 7b61e3387..00a68ed71 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -36,6 +36,7 @@ + @@ -58,6 +59,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 92340bf5e..d98ca1c1a 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -21,6 +21,7 @@ + @@ -38,6 +39,7 @@ + diff --git a/src/duckstation/duckstation.vcxproj b/src/duckstation/duckstation.vcxproj index e43390bfc..33edb6036 100644 --- a/src/duckstation/duckstation.vcxproj +++ b/src/duckstation/duckstation.vcxproj @@ -50,12 +50,14 @@ + + diff --git a/src/duckstation/duckstation.vcxproj.filters b/src/duckstation/duckstation.vcxproj.filters index cd9a6b02b..60ebd5f6d 100644 --- a/src/duckstation/duckstation.vcxproj.filters +++ b/src/duckstation/duckstation.vcxproj.filters @@ -4,10 +4,12 @@ + + \ No newline at end of file diff --git a/src/duckstation/sdl_audio_stream.cpp b/src/duckstation/sdl_audio_stream.cpp new file mode 100644 index 000000000..093eb7c70 --- /dev/null +++ b/src/duckstation/sdl_audio_stream.cpp @@ -0,0 +1,61 @@ +#include "sdl_audio_stream.h" +#include "YBaseLib/Assert.h" +#include "YBaseLib/Log.h" +#include +Log_SetChannel(SDLAudioStream); + +SDLAudioStream::SDLAudioStream() = default; + +SDLAudioStream::~SDLAudioStream() +{ + if (m_is_open) + SDLAudioStream::CloseDevice(); +} + +bool SDLAudioStream::OpenDevice() +{ + DebugAssert(!m_is_open); + + SDL_AudioSpec spec = {}; + spec.freq = m_output_sample_rate; + spec.channels = m_channels; + spec.format = AUDIO_S16; + spec.samples = m_buffer_size; + spec.callback = AudioCallback; + spec.userdata = static_cast(this); + + SDL_AudioSpec obtained = {}; + if (SDL_OpenAudio(&spec, &obtained) < 0) + { + Log_ErrorPrintf("SDL_OpenAudio failed"); + return false; + } + + m_is_open = true; + return true; +} + +void SDLAudioStream::PauseDevice(bool paused) +{ + SDL_PauseAudio(paused ? 1 : 0); +} + +void SDLAudioStream::CloseDevice() +{ + DebugAssert(m_is_open); + SDL_CloseAudio(); + m_is_open = false; +} + +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)); + } +} diff --git a/src/duckstation/sdl_audio_stream.h b/src/duckstation/sdl_audio_stream.h new file mode 100644 index 000000000..a3daf689c --- /dev/null +++ b/src/duckstation/sdl_audio_stream.h @@ -0,0 +1,19 @@ +#pragma once +#include "common/audio_stream.h" +#include + +class SDLAudioStream final : public AudioStream +{ +public: + SDLAudioStream(); + ~SDLAudioStream(); + +protected: + bool OpenDevice() override; + void PauseDevice(bool paused) override; + void CloseDevice() override; + + static void AudioCallback(void* userdata, uint8_t* stream, int len); + + bool m_is_open = false; +};