diff --git a/src/common/audio_stream.h b/src/common/audio_stream.h index 1160e6f36..ea8e73e0c 100644 --- a/src/common/audio_stream.h +++ b/src/common/audio_stream.h @@ -41,6 +41,10 @@ public: void WriteSamples(const SampleType* samples, u32 num_samples); void EndWrite(u32 num_samples); + static std::unique_ptr CreateNullAudioStream(); + + static std::unique_ptr CreateCubebAudioStream(); + protected: virtual bool OpenDevice() = 0; virtual void PauseDevice(bool paused) = 0; diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 9dc19f2f8..f29ab73f5 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -42,6 +42,7 @@ + @@ -74,6 +75,7 @@ + @@ -99,6 +101,9 @@ + + {72f9423c-91ee-4487-aac6-555ed6f61aa1} + {43540154-9e1e-409c-834f-b84be5621388} @@ -243,7 +248,7 @@ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 true @@ -269,7 +274,7 @@ _ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default false true @@ -298,7 +303,7 @@ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 true @@ -324,7 +329,7 @@ _ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default false true @@ -354,7 +359,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 false @@ -384,7 +389,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 @@ -414,7 +419,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 false @@ -444,7 +449,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index bb293b537..5e9a2e99b 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -46,6 +46,7 @@ + @@ -88,6 +89,7 @@ + diff --git a/src/common/cubeb_audio_stream.cpp b/src/common/cubeb_audio_stream.cpp new file mode 100644 index 000000000..1a725ca53 --- /dev/null +++ b/src/common/cubeb_audio_stream.cpp @@ -0,0 +1,120 @@ +#include "cubeb_audio_stream.h" +#include "common/assert.h" +#include "common/log.h" +Log_SetChannel(CubebAudioStream); + +CubebAudioStream::CubebAudioStream() = default; + +CubebAudioStream::~CubebAudioStream() +{ + if (IsOpen()) + CubebAudioStream::CloseDevice(); +} + +bool CubebAudioStream::OpenDevice() +{ + Assert(!IsOpen()); + + int rv = cubeb_init(&m_cubeb_context, "DuckStation", nullptr); + if (rv != CUBEB_OK) + { + Log_ErrorPrintf("Could not initialize cubeb context: %d", rv); + return false; + } + + cubeb_stream_params params = {}; + params.format = CUBEB_SAMPLE_S16LE; + params.rate = m_output_sample_rate; + params.channels = m_channels; + params.layout = CUBEB_LAYOUT_UNDEFINED; + params.prefs = CUBEB_STREAM_PREF_NONE; + + u32 latency_frames = 0; + rv = cubeb_get_min_latency(m_cubeb_context, ¶ms, &latency_frames); + if (rv != CUBEB_OK) + { + Log_ErrorPrintf("Could not get minimum latency: %d", rv); + cubeb_destroy(m_cubeb_context); + m_cubeb_context = nullptr; + return false; + } + + 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); + else + latency_frames = m_buffer_size; + + char stream_name[32]; + std::snprintf(stream_name, sizeof(stream_name), "AudioStream_%p", this); + + rv = cubeb_stream_init(m_cubeb_context, &m_cubeb_stream, stream_name, nullptr, nullptr, nullptr, ¶ms, + latency_frames, DataCallback, StateCallback, this); + if (rv != CUBEB_OK) + { + Log_ErrorPrintf("Could not create stream: %d", rv); + cubeb_destroy(m_cubeb_context); + m_cubeb_context = nullptr; + return false; + } + + return true; +} + +void CubebAudioStream::PauseDevice(bool paused) +{ + if (paused == m_paused) + return; + + int rv = paused ? cubeb_stream_stop(m_cubeb_stream) : cubeb_stream_start(m_cubeb_stream); + if (rv != CUBEB_OK) + { + Log_ErrorPrintf("cubeb_stream_%s failed: %d", paused ? "stop" : "start", rv); + return; + } +} + +void CubebAudioStream::CloseDevice() +{ + Assert(IsOpen()); + + if (!m_paused) + cubeb_stream_stop(m_cubeb_stream); + + cubeb_stream_destroy(m_cubeb_stream); + m_cubeb_stream = nullptr; + + cubeb_destroy(m_cubeb_context); + m_cubeb_context = nullptr; +} + +long CubebAudioStream::DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer, + long nframes) +{ + 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) + { + std::memset(reinterpret_cast(output_buffer) + (read_frames * this_ptr->m_channels), 0, + silence_frames * this_ptr->m_channels * sizeof(SampleType)); + } + + return nframes; +} + +void CubebAudioStream::StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state) +{ + CubebAudioStream* const this_ptr = static_cast(user_ptr); + + this_ptr->m_paused = (state != CUBEB_STATE_STARTED); +} + +void CubebAudioStream::BufferAvailable() {} + +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 new file mode 100644 index 000000000..a63c77be3 --- /dev/null +++ b/src/common/cubeb_audio_stream.h @@ -0,0 +1,27 @@ +#pragma once +#include "common/audio_stream.h" +#include "cubeb/cubeb.h" +#include + +class CubebAudioStream final : public AudioStream +{ +public: + CubebAudioStream(); + ~CubebAudioStream(); + +protected: + bool IsOpen() const { return m_cubeb_stream != nullptr; } + + bool OpenDevice() override; + void PauseDevice(bool paused) override; + void CloseDevice() override; + void BufferAvailable() override; + + static long DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer, + long nframes); + static void StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state); + + cubeb* m_cubeb_context = nullptr; + cubeb_stream* m_cubeb_stream = nullptr; + bool m_paused = true; +}; diff --git a/src/common/null_audio_stream.cpp b/src/common/null_audio_stream.cpp index d6c5794b4..f8f4662f2 100644 --- a/src/common/null_audio_stream.cpp +++ b/src/common/null_audio_stream.cpp @@ -19,7 +19,7 @@ void NullAudioStream::BufferAvailable() DropBuffer(); } -std::unique_ptr NullAudioStream::Create() +std::unique_ptr AudioStream::CreateNullAudioStream() { - return std::unique_ptr(new NullAudioStream()); + return std::make_unique(); } diff --git a/src/common/null_audio_stream.h b/src/common/null_audio_stream.h index d589ef697..40a6a243b 100644 --- a/src/common/null_audio_stream.h +++ b/src/common/null_audio_stream.h @@ -4,16 +4,12 @@ class NullAudioStream final : public AudioStream { public: + NullAudioStream(); ~NullAudioStream(); - static std::unique_ptr Create(); - protected: bool OpenDevice() override; void PauseDevice(bool paused) override; void CloseDevice() override; void BufferAvailable() override; - -private: - NullAudioStream(); }; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 53d1da168..e9afa029a 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -2,7 +2,6 @@ #include "common/assert.h" #include "common/byte_stream.h" #include "common/log.h" -#include "common/null_audio_stream.h" #include "common/string_util.h" #include "core/controller.h" #include "core/game_list.h" @@ -454,7 +453,7 @@ void QtHostInterface::createAudioStream() // fall back to null output m_audio_stream.reset(); - m_audio_stream = NullAudioStream::Create(); + m_audio_stream = AudioStream::CreateNullAudioStream(); m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, AUDIO_BUFFER_SIZE, 4); } } diff --git a/src/duckstation/sdl_host_interface.cpp b/src/duckstation/sdl_host_interface.cpp index 6bc50d263..5656e8c4e 100644 --- a/src/duckstation/sdl_host_interface.cpp +++ b/src/duckstation/sdl_host_interface.cpp @@ -2,7 +2,6 @@ #include "common/assert.h" #include "common/byte_stream.h" #include "common/log.h" -#include "common/null_audio_stream.h" #include "common/string_util.h" #include "core/controller.h" #include "core/gpu.h" @@ -126,7 +125,7 @@ void SDLHostInterface::CreateAudioStream() switch (m_settings.audio_backend) { case AudioBackend::Null: - m_audio_stream = NullAudioStream::Create(); + m_audio_stream = AudioStream::CreateNullAudioStream(); break; case AudioBackend::Default: @@ -139,7 +138,7 @@ void SDLHostInterface::CreateAudioStream() { ReportError("Failed to recreate audio stream, falling back to null"); m_audio_stream.reset(); - m_audio_stream = NullAudioStream::Create(); + m_audio_stream = AudioStream::CreateNullAudioStream(); if (!m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS)) Panic("Failed to reconfigure null audio stream"); }