diff --git a/src/common/audio_stream.cpp b/src/common/audio_stream.cpp index 7599967d1..903a095a0 100644 --- a/src/common/audio_stream.cpp +++ b/src/common/audio_stream.cpp @@ -123,6 +123,18 @@ void AudioStream::EndWrite(u32 num_samples) m_buffer_mutex.unlock(); } +u32 AudioStream::GetSamplesAvailable() const +{ + // TODO: Use atomic loads + u32 available_buffers; + { + std::unique_lock lock(m_buffer_mutex); + available_buffers = m_num_available_buffers; + } + + return available_buffers * m_buffer_size; +} + u32 AudioStream::ReadSamples(SampleType* samples, u32 num_samples) { u32 remaining_samples = num_samples; diff --git a/src/common/audio_stream.h b/src/common/audio_stream.h index 7900863b8..1160e6f36 100644 --- a/src/common/audio_stream.h +++ b/src/common/audio_stream.h @@ -49,6 +49,7 @@ protected: bool IsDeviceOpen() const { return (m_output_sample_rate > 0); } + u32 GetSamplesAvailable() const; u32 ReadSamples(SampleType* samples, u32 num_samples); void DropBuffer(); @@ -69,7 +70,7 @@ private: void EnsureBuffer(); std::vector m_buffers; - std::mutex m_buffer_mutex; + mutable std::mutex m_buffer_mutex; // For input. u32 m_first_free_buffer = 0; diff --git a/src/core/host_interface.h b/src/core/host_interface.h index 4f27b8bf0..c0fa2e5d2 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -53,6 +53,14 @@ public: protected: using ThrottleClock = std::chrono::steady_clock; + enum : u32 + { + AUDIO_SAMPLE_RATE = 44100, + AUDIO_CHANNELS = 2, + AUDIO_BUFFER_SIZE = 2048, + AUDIO_BUFFERS = 2 + }; + struct OSDMessage { std::string text; diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index c4a412b8b..87ee6c030 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -24,6 +24,8 @@ add_executable(duckstation-qt opengldisplaywindow.h portsettingswidget.cpp portsettingswidget.h + qtaudiostream.cpp + qtaudiostream.h qtdisplaywindow.cpp qtdisplaywindow.h qthostinterface.cpp diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 613cab401..16d862f4d 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -39,6 +39,7 @@ + @@ -57,6 +58,7 @@ + @@ -281,7 +283,7 @@ Console true $(SolutionDir)dep\msvc\lib32-debug;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories) - Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;%(AdditionalDependencies) + Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5Multimediad.lib;%(AdditionalDependencies) @@ -302,7 +304,7 @@ Console true $(SolutionDir)dep\msvc\lib64-debug;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories) - Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;%(AdditionalDependencies) + Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5Multimediad.lib;%(AdditionalDependencies) @@ -325,7 +327,7 @@ Console true $(SolutionDir)dep\msvc\lib32-debug;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories) - Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;%(AdditionalDependencies) + Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5Multimediad.lib;%(AdditionalDependencies) @@ -348,7 +350,7 @@ Console true $(SolutionDir)dep\msvc\lib64-debug;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories) - Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;%(AdditionalDependencies) + Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5Multimediad.lib;%(AdditionalDependencies) @@ -370,7 +372,7 @@ true true $(SolutionDir)dep\msvc\lib32;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories) - Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;%(AdditionalDependencies) + Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5Multimedia.lib;%(AdditionalDependencies) @@ -393,7 +395,7 @@ true true $(SolutionDir)dep\msvc\lib32;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories) - Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;%(AdditionalDependencies) + Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5Multimedia.lib;%(AdditionalDependencies) UseLinkTimeCodeGeneration @@ -416,7 +418,7 @@ true true $(SolutionDir)dep\msvc\lib64;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories) - Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;%(AdditionalDependencies) + Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5Multimedia.lib;%(AdditionalDependencies) @@ -439,7 +441,7 @@ true true $(SolutionDir)dep\msvc\lib64;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories) - Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;%(AdditionalDependencies) + Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5Multimedia.lib;%(AdditionalDependencies) UseLinkTimeCodeGeneration diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index ff3a57363..64fd7d469 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -27,13 +27,15 @@ + + + - - + @@ -54,6 +56,8 @@ + + diff --git a/src/duckstation-qt/qtaudiostream.cpp b/src/duckstation-qt/qtaudiostream.cpp new file mode 100644 index 000000000..9a1f9628b --- /dev/null +++ b/src/duckstation-qt/qtaudiostream.cpp @@ -0,0 +1,84 @@ +#include "qtaudiostream.h" +#include +#include + +QtAudioStream::QtAudioStream() +{ + QIODevice::open(QIODevice::ReadOnly); +} + +QtAudioStream::~QtAudioStream() = default; + +std::unique_ptr QtAudioStream::Create() +{ + return std::make_unique(); +} + +bool QtAudioStream::OpenDevice() +{ + QAudioFormat format; + format.setSampleRate(m_output_sample_rate); + format.setChannelCount(m_channels); + format.setSampleSize(sizeof(SampleType) * 8); + format.setCodec("audio/pcm"); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setSampleType(QAudioFormat::SignedInt); + + QAudioDeviceInfo adi = QAudioDeviceInfo::defaultOutputDevice(); + if (!adi.isFormatSupported(format)) + { + qWarning() << "Audio format not supported by device"; + return false; + } + + m_output = std::make_unique(format); + m_output->setBufferSize(sizeof(SampleType) * m_channels * m_buffer_size); + return true; +} + +void QtAudioStream::PauseDevice(bool paused) +{ + if (paused) + { + m_output->stop(); + return; + } + + m_output->start(this); +} + +void QtAudioStream::CloseDevice() +{ + m_output.reset(); +} + +void QtAudioStream::BufferAvailable() {} + +bool QtAudioStream::isSequential() const +{ + return true; +} + +qint64 QtAudioStream::bytesAvailable() const +{ + return GetSamplesAvailable() * m_channels * sizeof(SampleType); +} + +qint64 QtAudioStream::readData(char* data, qint64 maxlen) +{ + const u32 num_samples = static_cast(maxlen) / sizeof(SampleType) / m_channels; + const u32 read_samples = ReadSamples(reinterpret_cast(data), num_samples); + const u32 silence_samples = num_samples - read_samples; + if (silence_samples > 0) + { + std::memset(reinterpret_cast(data) + (read_samples * m_channels), 0, + silence_samples * m_channels * sizeof(SampleType)); + } + + return num_samples * m_channels * sizeof(SampleType); +} + +qint64 QtAudioStream::writeData(const char* data, qint64 len) +{ + return 0; +} diff --git a/src/duckstation-qt/qtaudiostream.h b/src/duckstation-qt/qtaudiostream.h new file mode 100644 index 000000000..ad3d1b5fe --- /dev/null +++ b/src/duckstation-qt/qtaudiostream.h @@ -0,0 +1,29 @@ +#pragma once +#include "common/audio_stream.h" +#include +#include + +class QAudioOutput; + +class QtAudioStream final : public AudioStream, private QIODevice +{ +public: + QtAudioStream(); + ~QtAudioStream(); + + static std::unique_ptr Create(); + +protected: + bool OpenDevice() override; + void PauseDevice(bool paused) override; + void CloseDevice() override; + void BufferAvailable() override; + +private: + bool isSequential() const override; + qint64 bytesAvailable() const override; + qint64 readData(char* data, qint64 maxlen) override; + qint64 writeData(const char* data, qint64 len) override; + + std::unique_ptr m_output; +}; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 0ad22324c..1255ed2ad 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -6,6 +6,7 @@ #include "core/game_list.h" #include "core/gpu.h" #include "core/system.h" +#include "qtaudiostream.h" #include "qtsettingsinterface.h" #include "qtutils.h" #include @@ -20,6 +21,7 @@ QtHostInterface::QtHostInterface(QObject* parent) checkSettings(); createGameList(); doUpdateInputMap(); + createAudioStream(); createThread(); } @@ -328,6 +330,8 @@ void QtHostInterface::powerOffSystem() } m_system.reset(); + m_audio_stream->PauseOutput(true); + m_audio_stream->EmptyBuffers(); m_display_window->destroyDeviceContext(); emit emulationStopped(); @@ -359,6 +363,7 @@ void QtHostInterface::pauseSystem(bool paused) } m_paused = paused; + m_audio_stream->PauseOutput(paused); emit emulationPaused(paused); } @@ -372,9 +377,6 @@ void QtHostInterface::doBootSystem(QString initial_filename, QString initial_sav return; } - m_audio_stream = NullAudioStream::Create(); - m_audio_stream->Reconfigure(); - std::string initial_filename_str = initial_filename.toStdString(); std::string initial_save_state_filename_str = initial_save_state_filename.toStdString(); if (!CreateSystem() || @@ -386,9 +388,25 @@ void QtHostInterface::doBootSystem(QString initial_filename, QString initial_sav return; } + m_audio_stream->PauseOutput(false); emit emulationStarted(); } +void QtHostInterface::createAudioStream() +{ + // Qt at least on Windows seems to want a buffer size of at least 8KB. + m_audio_stream = QtAudioStream::Create(); + if (!m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, AUDIO_BUFFER_SIZE, 4)) + { + qWarning() << "Failed to configure audio stream, falling back to null output"; + + // fall back to null output + m_audio_stream.reset(); + m_audio_stream = NullAudioStream::Create(); + m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, AUDIO_BUFFER_SIZE, 4); + } +} + void QtHostInterface::createThread() { m_original_thread = QThread::currentThread(); diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 8e621a3b9..1f661db8f 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -106,6 +106,7 @@ private: void updateControllerInputMap(); void updateHotkeyInputMap(); void addButtonToInputMap(const QString& binding, InputButtonHandler handler); + void createAudioStream(); void createThread(); void stopThread(); void threadEntryPoint(); diff --git a/src/duckstation/sdl_host_interface.cpp b/src/duckstation/sdl_host_interface.cpp index fb6246add..c1564f0fb 100644 --- a/src/duckstation/sdl_host_interface.cpp +++ b/src/duckstation/sdl_host_interface.cpp @@ -135,12 +135,12 @@ void SDLHostInterface::CreateAudioStream() break; } - if (!m_audio_stream->Reconfigure(44100, 2)) + if (!m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS)) { ReportError("Failed to recreate audio stream, falling back to null"); m_audio_stream.reset(); m_audio_stream = NullAudioStream::Create(); - if (!m_audio_stream->Reconfigure(44100, 2)) + if (!m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS)) Panic("Failed to reconfigure null audio stream"); } }