From 06433d614acdb46ba4a7f8635445eafe8038d0bd Mon Sep 17 00:00:00 2001 From: Oschowa Date: Thu, 24 Oct 2019 21:26:29 +0200 Subject: [PATCH] Implement FAudio backend (#6374) --- .gitmodules | 3 + .travis.yml | 5 + .travis/build-linux.bash | 2 +- .travis/build-mac.bash | 2 +- 3rdparty/CMakeLists.txt | 12 ++ 3rdparty/FAudio | 1 + BUILDING.md | 5 +- CMakeLists.txt | 1 + appveyor.yml | 1 + rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp | 179 +++++++++++++++++++++++ rpcs3/Emu/Audio/FAudio/FAudioBackend.h | 44 ++++++ rpcs3/Emu/CMakeLists.txt | 3 +- rpcs3/Emu/System.cpp | 3 + rpcs3/Emu/System.h | 3 + rpcs3/main_application.cpp | 6 + 15 files changed, 265 insertions(+), 5 deletions(-) create mode 160000 3rdparty/FAudio create mode 100644 rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp create mode 100644 rpcs3/Emu/Audio/FAudio/FAudioBackend.h diff --git a/.gitmodules b/.gitmodules index d3be5fa7a2..911d986ea8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -52,3 +52,6 @@ path = 3rdparty/libusb url = https://github.com/RPCS3/libusb.git ignore = dirty +[submodule "3rdparty/FAudio"] + path = 3rdparty/FAudio + url = https://github.com/FNA-XNA/FAudio.git diff --git a/.travis.yml b/.travis.yml index 0ecc8a39b2..253ff1d3ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,11 @@ matrix: services: docker cache: ccache install: "docker pull rpcs3/rpcs3-travis-xenial:1.0" + addons: + apt: + packages: + - libsdl2-2.0 + - libsdl2-dev script: 'travis_wait docker run -v $(pwd):/rpcs3 -v "$HOME/.ccache":/root/.ccache --env-file .travis/travis.env rpcs3/rpcs3-travis-xenial:1.0 /bin/bash -ex /rpcs3/.travis/build-linux.bash' - os: osx osx_image: xcode10.2 diff --git a/.travis/build-linux.bash b/.travis/build-linux.bash index 5944fbcb6e..dd991b5f53 100644 --- a/.travis/build-linux.bash +++ b/.travis/build-linux.bash @@ -8,7 +8,7 @@ export LD_LIBRARY_PATH=$QT_BASE_DIR/lib/x86_64-linux-gnu:$QT_BASE_DIR/lib cd rpcs3 -git submodule update --quiet --init asmjit 3rdparty/ffmpeg 3rdparty/pugixml 3rdparty/GSL 3rdparty/libpng 3rdparty/cereal 3rdparty/hidapi 3rdparty/xxHash 3rdparty/yaml-cpp 3rdparty/libusb Vulkan/glslang +git submodule update --quiet --init asmjit 3rdparty/ffmpeg 3rdparty/pugixml 3rdparty/GSL 3rdparty/libpng 3rdparty/cereal 3rdparty/hidapi 3rdparty/xxHash 3rdparty/yaml-cpp 3rdparty/libusb 3rdparty/FAudio Vulkan/glslang # Download pre-compiled llvm libs curl -sLO https://github.com/RPCS3/llvm/releases/download/continuous-linux-master/llvmlibs-linux.tar.gz diff --git a/.travis/build-mac.bash b/.travis/build-mac.bash index 7de63b047a..6b16765045 100644 --- a/.travis/build-mac.bash +++ b/.travis/build-mac.bash @@ -15,7 +15,7 @@ cp target/release/libportability.dylib vulkan-sdk/lib/libVulkan.dylib install_name_tool -id ${PWD}/vulkan-sdk/lib/libVulkan.dylib vulkan-sdk/lib/libVulkan.dylib export VULKAN_SDK=${PWD}/vulkan-sdk -git submodule update --quiet --init asmjit 3rdparty/ffmpeg 3rdparty/pugixml 3rdparty/GSL 3rdparty/libpng 3rdparty/cereal 3rdparty/hidapi 3rdparty/libusb 3rdparty/xxHash 3rdparty/yaml-cpp Vulkan/glslang +git submodule update --quiet --init asmjit 3rdparty/ffmpeg 3rdparty/pugixml 3rdparty/GSL 3rdparty/libpng 3rdparty/cereal 3rdparty/hidapi 3rdparty/libusb 3rdparty/xxHash 3rdparty/yaml-cpp 3rdparty/FAudio Vulkan/glslang mkdir build; cd build cmake .. -DWITH_LLVM=OFF -DUSE_NATIVE_INSTRUCTIONS=OFF -G Ninja diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 97e1f9689b..5de99ee24a 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -337,6 +337,17 @@ add_library(3rdparty_openal INTERFACE) target_include_directories(3rdparty_openal INTERFACE ${OPENAL_INCLUDE_DIR}) target_link_libraries(3rdparty_openal INTERFACE ${OPENAL_LIBRARY}) +# FAudio +set(FAUDIO_TARGET 3rdparty_dummy_lib) +if(USE_FAUDIO) + # FAudio depends on SDL2 + pkg_check_modules(SDL2 sdl2) + if(SDL2_FOUND) + add_subdirectory(FAudio EXCLUDE_FROM_ALL) + target_compile_definitions(FAudio INTERFACE -DHAVE_FAUDIO) + set(FAUDIO_TARGET FAudio) + endif() +endif() # FFMPEG add_library(3rdparty_ffmpeg INTERFACE) @@ -416,6 +427,7 @@ add_library(3rdparty::stblib ALIAS 3rdparty_stblib) add_library(3rdparty::discord-rpc ALIAS 3rdparty_discord-rpc) add_library(3rdparty::alsa ALIAS ${ALSA_TARGET}) add_library(3rdparty::pulse ALIAS ${PULSE_TARGET}) +add_library(3rdparty::faudio ALIAS ${FAUDIO_TARGET}) add_library(3rdparty::libevdev ALIAS ${LIBEVDEV_TARGET}) add_library(3rdparty::vulkan ALIAS ${VULKAN_TARGET}) add_library(3rdparty::openal ALIAS 3rdparty_openal) diff --git a/3rdparty/FAudio b/3rdparty/FAudio new file mode 160000 index 0000000000..ceadb9b14c --- /dev/null +++ b/3rdparty/FAudio @@ -0,0 +1 @@ +Subproject commit ceadb9b14ca256f82b0c0c344aef742dfea92779 diff --git a/BUILDING.md b/BUILDING.md index 144239753f..03dc7ee631 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -23,16 +23,17 @@ These are the essentials tools to build RPCS3 on Linux. Some of them can be inst * [CMake 3.8.2+](https://www.cmake.org/download/) * [Qt 5.10+](https://www.qt.io/download-qt-installer) (Avoid 5.11.1, due to a bug) * [Vulkan SDK 1.1.97.0+](https://vulkan.lunarg.com/sdk/home) (See "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html)) +* [SDL2](https://www.libsdl.org/download-2.0.php) (for the FAudio backend) **If you have an NVIDIA GPU, you may need to install the libglvnd package.** #### Arch Linux - sudo pacman -S glew openal cmake vulkan-validation-layers qt5-base qt5-declarative + sudo pacman -S glew openal cmake vulkan-validation-layers qt5-base qt5-declarative sdl2 #### Debian & Ubuntu - sudo apt-get install cmake build-essential libasound2-dev libpulse-dev libopenal-dev libglew-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git qt5-default libevdev-dev qtdeclarative5-dev qtbase5-private-dev + sudo apt-get install cmake build-essential libasound2-dev libpulse-dev libopenal-dev libglew-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git qt5-default libevdev-dev qtdeclarative5-dev qtbase5-private-dev libsdl2-2.0 libsdl2-dev ##### GCC 8.x installation diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cabeba79a..71f5f7bfb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ option(WITH_LLVM "Enable usage of LLVM library" ON) option(BUILD_LLVM_SUBMODULE "Build LLVM from git submodule" ON) option(USE_ALSA "ALSA audio backend" ON) option(USE_PULSE "PulseAudio audio backend" ON) +option(USE_FAUDIO "FAudio audio backend" ON) option(USE_LIBEVDEV "libevdev-based joystick support" ON) option(USE_DISCORD_RPC "Discord rich presence integration" ON) option(USE_SYSTEM_ZLIB "Prefer system ZLIB instead of the builtin one" ON) diff --git a/appveyor.yml b/appveyor.yml index cfd162dfea..fd65e62f02 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -55,6 +55,7 @@ install: 3rdparty/xxHash ` 3rdparty/yaml-cpp ` 3rdparty/zlib ` + 3rdparty/FAudio ` asmjit ` Vulkan/glslang diff --git a/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp b/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp new file mode 100644 index 0000000000..ca14fd13c9 --- /dev/null +++ b/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp @@ -0,0 +1,179 @@ +#include "FAudioBackend.h" + +#ifdef HAVE_FAUDIO + +FAudioBackend::FAudioBackend() +{ + u32 res; + + res = FAudioCreate(&m_instance, 0, FAUDIO_DEFAULT_PROCESSOR); + if (res) + { + fmt::throw_exception("FAudioCreate() failed(0x%08x)", res); + } + + res = FAudio_CreateMasteringVoice(m_instance, &m_master_voice, g_cfg.audio.downmix_to_2ch ? 2 : 8, 48000, 0, 0, nullptr); + if (res) + { + fmt::throw_exception("FAudio_CreateMasteringVoice() failed(0x%08x)", res); + } +} + +FAudioBackend::~FAudioBackend() +{ + if (m_source_voice != nullptr) + { + FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW); + FAudioVoice_DestroyVoice(m_source_voice); + } + + if (m_master_voice != nullptr) + { + FAudioVoice_DestroyVoice(m_master_voice); + } + + if (m_instance != nullptr) + { + FAudio_StopEngine(m_instance); + FAudio_Release(m_instance); + } +} + +void FAudioBackend::Play() +{ + AUDIT(m_source_voice != nullptr); + + u32 res = FAudioSourceVoice_Start(m_source_voice, 0, FAUDIO_COMMIT_NOW); + if (res) + { + LOG_ERROR(GENERAL, "FAudioSourceVoice_Start() failed(0x%08x)", res); + Emu.Pause(); + } +} + +void FAudioBackend::Pause() +{ + AUDIT(m_source_voice != nullptr); + + u32 res = FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW); + if (res) + { + LOG_ERROR(GENERAL, "FAudioSourceVoice_Stop() failed(0x%08x)", res); + Emu.Pause(); + } +} + +void FAudioBackend::Flush() +{ + AUDIT(m_source_voice != nullptr); + + u32 res = FAudioSourceVoice_FlushSourceBuffers(m_source_voice); + if (res) + { + LOG_ERROR(GENERAL, "FAudioSourceVoice_FlushSourceBuffers() failed(0x%08x)", res); + Emu.Pause(); + } +} + +bool FAudioBackend::IsPlaying() +{ + AUDIT(m_source_voice != nullptr); + + FAudioVoiceState state; + FAudioSourceVoice_GetState(m_source_voice, &state, FAUDIO_VOICE_NOSAMPLESPLAYED); + + return state.BuffersQueued > 0 || state.pCurrentBufferContext != nullptr; +} + +void FAudioBackend::Close() +{ + Pause(); + Flush(); +} + +void FAudioBackend::Open(u32 /* num_buffers */) +{ + const u32 sample_size = AudioBackend::get_sample_size(); + const u32 channels = AudioBackend::get_channels(); + const u32 sampling_rate = AudioBackend::get_sampling_rate(); + + FAudioWaveFormatEx waveformatex; + waveformatex.wFormatTag = g_cfg.audio.convert_to_u16 ? FAUDIO_FORMAT_PCM : FAUDIO_FORMAT_IEEE_FLOAT; + waveformatex.nChannels = channels; + waveformatex.nSamplesPerSec = sampling_rate; + waveformatex.nAvgBytesPerSec = static_cast(sampling_rate * channels * sample_size); + waveformatex.nBlockAlign = channels * sample_size; + waveformatex.wBitsPerSample = sample_size * 8; + waveformatex.cbSize = 0; + + u32 res = FAudio_CreateSourceVoice(m_instance, &m_source_voice, &waveformatex, 0, FAUDIO_DEFAULT_FREQ_RATIO, nullptr, nullptr, nullptr); + if (res) + { + LOG_ERROR(GENERAL, "FAudio_CreateSourceVoice() failed(0x%08x)", res); + Emu.Pause(); + } + + AUDIT(m_source_voice != nullptr); + FAudioVoice_SetVolume(m_source_voice, channels == 2 ? 1.0f : 4.0f, FAUDIO_COMMIT_NOW); +} + +bool FAudioBackend::AddData(const void* src, u32 num_samples) +{ + AUDIT(m_source_voice != nullptr); + + FAudioVoiceState state; + FAudioSourceVoice_GetState(m_source_voice, &state, FAUDIO_VOICE_NOSAMPLESPLAYED); + + if (state.BuffersQueued >= MAX_AUDIO_BUFFERS) + { + LOG_WARNING(GENERAL, "XAudio2Backend : too many buffers enqueued (%d)", state.BuffersQueued); + return false; + } + + FAudioBuffer buffer; + buffer.AudioBytes = num_samples * AudioBackend::get_sample_size(); + buffer.Flags = 0; + buffer.LoopBegin = FAUDIO_NO_LOOP_REGION; + buffer.LoopCount = 0; + buffer.LoopLength = 0; + buffer.pAudioData = static_cast(src); + buffer.pContext = 0; + buffer.PlayBegin = 0; + buffer.PlayLength = AUDIO_BUFFER_SAMPLES; + + u32 res = FAudioSourceVoice_SubmitSourceBuffer(m_source_voice, &buffer, nullptr); + if (res) + { + LOG_ERROR(GENERAL, "FAudioSourceVoice_SubmitSourceBuffer() failed(0x%08x)", res); + Emu.Pause(); + return false; + } + + return true; +} + +u64 FAudioBackend::GetNumEnqueuedSamples() +{ + FAudioVoiceState state; + FAudioSourceVoice_GetState(m_source_voice, &state, 0); + + // all buffers contain AUDIO_BUFFER_SAMPLES, so we can easily calculate how many samples there are remaining + return (AUDIO_BUFFER_SAMPLES - state.SamplesPlayed % AUDIO_BUFFER_SAMPLES) + (state.BuffersQueued * AUDIO_BUFFER_SAMPLES); +} + +f32 FAudioBackend::SetFrequencyRatio(f32 new_ratio) +{ + new_ratio = std::clamp(new_ratio, FAUDIO_MIN_FREQ_RATIO, FAUDIO_DEFAULT_FREQ_RATIO); + + u32 res = FAudioSourceVoice_SetFrequencyRatio(m_source_voice, new_ratio, FAUDIO_COMMIT_NOW); + if (res) + { + LOG_ERROR(GENERAL, "FAudioSourceVoice_SetFrequencyRatio() failed(0x%08x)", res); + Emu.Pause(); + return 1.0f; + } + + return new_ratio; +} + +#endif diff --git a/rpcs3/Emu/Audio/FAudio/FAudioBackend.h b/rpcs3/Emu/Audio/FAudio/FAudioBackend.h new file mode 100644 index 0000000000..c2d8c7af5c --- /dev/null +++ b/rpcs3/Emu/Audio/FAudio/FAudioBackend.h @@ -0,0 +1,44 @@ +#pragma once + +#ifdef HAVE_FAUDIO + +#include "Emu/Audio/AudioBackend.h" +#include "3rdparty/FAudio/include/FAudio.h" + +class FAudioBackend : public AudioBackend +{ +private: + FAudio* m_instance; + FAudioMasteringVoice* m_master_voice; + FAudioSourceVoice* m_source_voice = nullptr; + +public: + FAudioBackend(); + virtual ~FAudioBackend() override; + + virtual const char* GetName() const override + { + return "FAudio"; + }; + + static const u32 capabilities = PLAY_PAUSE_FLUSH | IS_PLAYING | GET_NUM_ENQUEUED_SAMPLES | SET_FREQUENCY_RATIO; + virtual u32 GetCapabilities() const override + { + return capabilities; + }; + + virtual void Open(u32 /* num_buffers */) override; + virtual void Close() override; + + virtual void Play() override; + virtual void Pause() override; + virtual bool IsPlaying() override; + + virtual bool AddData(const void* src, u32 num_samples) override; + virtual void Flush() override; + + virtual u64 GetNumEnqueuedSamples() override; + virtual f32 SetFrequencyRatio(f32 new_ratio) override; +}; + +#endif diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index c787a66870..2bc91fe9a5 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -90,6 +90,7 @@ target_sources(rpcs3_emu PRIVATE Audio/AL/OpenALBackend.cpp Audio/ALSA/ALSABackend.cpp Audio/Pulse/PulseBackend.cpp + Audio/FAudio/FAudioBackend.cpp ) if(WIN32) @@ -104,7 +105,7 @@ endif() target_link_libraries(rpcs3_emu PUBLIC - 3rdparty::alsa 3rdparty::pulse 3rdparty::openal) + 3rdparty::alsa 3rdparty::pulse 3rdparty::openal 3rdparty::faudio) # Cell diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 1fec53f81b..82b3b198cd 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -212,6 +212,9 @@ void fmt_class_string::format(std::string& out, u64 arg) case audio_renderer::pulse: return "PulseAudio"; #endif case audio_renderer::openal: return "OpenAL"; +#ifdef HAVE_FAUDIO + case audio_renderer::faudio: return "FAudio"; +#endif } return unknown; diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 8d0afa3998..628a643442 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -103,6 +103,9 @@ enum class audio_renderer #ifdef HAVE_PULSE pulse, #endif +#ifdef HAVE_FAUDIO + faudio, +#endif }; enum class camera_handler diff --git a/rpcs3/main_application.cpp b/rpcs3/main_application.cpp index bdd6258dfd..f233ea946c 100644 --- a/rpcs3/main_application.cpp +++ b/rpcs3/main_application.cpp @@ -31,6 +31,9 @@ #ifdef HAVE_PULSE #include "Emu/Audio/Pulse/PulseBackend.h" #endif +#ifdef HAVE_FAUDIO +#include "Emu/Audio/FAudio/FAudioBackend.h" +#endif #include "Emu/RSX/GSRender.h" #include "Emu/RSX/Null/NullGSRender.h" @@ -155,6 +158,9 @@ EmuCallbacks main_application::CreateCallbacks() #endif case audio_renderer::openal: return std::make_shared(); +#ifdef HAVE_FAUDIO + case audio_renderer::faudio: return std::make_shared(); +#endif default: fmt::throw_exception("Invalid audio renderer: %s" HERE, type); } };