diff --git a/.gitmodules b/.gitmodules index a11940fb53..2379e83933 100644 --- a/.gitmodules +++ b/.gitmodules @@ -68,3 +68,7 @@ path = 3rdparty/flatbuffers url = ../../google/flatbuffers.git ignore = dirty +[submodule "3rdparty/cubeb/cubeb"] + path = 3rdparty/cubeb/cubeb + url = ../../mozilla/cubeb.git + ignore = dirty diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 146e70cd7d..09783d3378 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -128,38 +128,8 @@ target_include_directories(3rdparty_stblib INTERFACE stblib/include) # DiscordRPC add_subdirectory(discord-rpc) - -# ALSA -set(ALSA_TARGET 3rdparty_dummy_lib) - -if(USE_ALSA) - find_package(ALSA) - if(ALSA_FOUND) - add_library(3rdparty_alsa INTERFACE) - target_compile_definitions(3rdparty_alsa INTERFACE -DHAVE_ALSA) - target_include_directories(3rdparty_alsa SYSTEM INTERFACE ${ALSA_INCLUDE_DIRS}) - target_link_libraries(3rdparty_alsa INTERFACE ${ALSA_LIBRARIES}) - - set(ALSA_TARGET 3rdparty_alsa) - endif() -endif() - - -# Pulse -set(PULSE_TARGET 3rdparty_dummy_lib) -if(USE_PULSE) - pkg_check_modules(PULSE libpulse-simple) - - if(PULSE_FOUND) - add_library(3rdparty_pulse INTERFACE) - target_compile_definitions(3rdparty_pulse INTERFACE -DHAVE_PULSE) - target_include_directories(3rdparty_pulse SYSTEM - INTERFACE ${PULSE_INCLUDE_DIRS}) - target_link_libraries(3rdparty_pulse INTERFACE ${PULSE_LDFLAGS}) - - set(PULSE_TARGET 3rdparty_pulse) - endif() -endif() +# Cubeb +add_subdirectory(cubeb EXCLUDE_FROM_ALL) # libevdev set(LIBEVDEV_TARGET 3rdparty_dummy_lib) @@ -334,8 +304,6 @@ add_library(3rdparty::libpng ALIAS ${LIBPNG_TARGET}) add_library(3rdparty::opengl ALIAS 3rdparty_opengl) add_library(3rdparty::stblib ALIAS 3rdparty_stblib) add_library(3rdparty::discordRPC ALIAS 3rdparty_discordRPC) -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}) diff --git a/3rdparty/cubeb/CMakeLists.txt b/3rdparty/cubeb/CMakeLists.txt new file mode 100644 index 0000000000..024dfba2ee --- /dev/null +++ b/3rdparty/cubeb/CMakeLists.txt @@ -0,0 +1,12 @@ +# Cubeb + +function(add_sanitizers ...) +endfunction(add_sanitizers) + +set(BUILD_SHARED_LIBS FALSE CACHE BOOL "Don't build shared libs") +set(BUILD_TESTS FALSE CACHE BOOL "Don't build tests") +set(BUILD_RUST_LIBS FALSE CACHE BOOL "Don't build rust libs") +set(BUILD_TOOLS FALSE CACHE BOOL "Don't build tools") + +add_subdirectory(cubeb EXCLUDE_FROM_ALL) +add_library(3rdparty::cubeb ALIAS cubeb) diff --git a/3rdparty/cubeb/cubeb b/3rdparty/cubeb/cubeb new file mode 160000 index 0000000000..d512bfa07a --- /dev/null +++ b/3rdparty/cubeb/cubeb @@ -0,0 +1 @@ +Subproject commit d512bfa07a327e0ae7e7aef892dcce01cbeaa67c diff --git a/3rdparty/cubeb/extra/cubeb_export.h b/3rdparty/cubeb/extra/cubeb_export.h new file mode 100644 index 0000000000..0a831f4ac4 --- /dev/null +++ b/3rdparty/cubeb/extra/cubeb_export.h @@ -0,0 +1,3 @@ +#pragma once + +#define CUBEB_EXPORT diff --git a/3rdparty/cubeb/libcubeb.vcxproj b/3rdparty/cubeb/libcubeb.vcxproj new file mode 100644 index 0000000000..2564552ab9 --- /dev/null +++ b/3rdparty/cubeb/libcubeb.vcxproj @@ -0,0 +1,87 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {fda7b080-03b0-48c8-b24f-88118981422a} + libcubeb + + + + + + StaticLibrary + false + false + Unicode + x64 + + + + + + + + + + + + + + + + + + TurnOffAllWarnings + false + OUTSIDE_SPEEX;RANDOM_PREFIX=speex;FLOATING_POINT;EXPORT=;USE_WASAPI;USE_WINMM;CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME;_HAS_DEPRECATED_RESULT_OF;%(PreprocessorDefinitions) + ./cubeb/src/speex/;./extra/;./cubeb/src/;./cubeb/include/;%(AdditionalIncludeDirectories) + + + + + + diff --git a/3rdparty/cubeb/libcubeb.vcxproj.filters b/3rdparty/cubeb/libcubeb.vcxproj.filters new file mode 100644 index 0000000000..ac215e9f40 --- /dev/null +++ b/3rdparty/cubeb/libcubeb.vcxproj.filters @@ -0,0 +1,108 @@ + + + + + Source files + + + Source files + + + Source files + + + Source files + + + Source files + + + Source files + + + Source files + + + Source files + + + Source files + + + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + Header files + + + + + {cd86de6d-e811-4c48-9653-af00c7098a45} + + + {566bbf3e-ca28-4390-b054-58c92a66f24e} + + + diff --git a/BUILDING.md b/BUILDING.md index 084aed7bcf..22b51537e9 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -29,11 +29,11 @@ These are the essentials tools to build RPCS3 on Linux. Some of them can be inst #### Arch Linux - sudo pacman -S glew openal cmake vulkan-validation-layers qt5-base qt5-declarative sdl2 + sudo pacman -S glew openal cmake vulkan-validation-layers qt5-base qt5-declarative sdl2 sndio jack2 #### Debian & Ubuntu - sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev libglew-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev + sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev libglew-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev libjack-dev libsndio-dev Ubuntu is usually horrendously out of date, and some packages need to be downloaded by hand. This part is for Qt, GCC, Vulkan, and CMake ##### Qt PPA @@ -82,11 +82,11 @@ sudo apt-get install cmake #### Fedora - sudo dnf install alsa-lib-devel cmake glew glew-devel libatomic libevdev-devel libudev-devel openal-devel qt5-qtbase-devel qt5-qtbase-private-devel vulkan-devel + sudo dnf install alsa-lib-devel cmake glew glew-devel libatomic libevdev-devel libudev-devel openal-devel qt5-qtbase-devel qt5-qtbase-private-devel vulkan-devel jack-audio-connection-kit-devel #### OpenSUSE - sudo zypper install git cmake libasound2 libpulse-devel openal-soft-devel glew-devel zlib-devel libedit-devel vulkan-devel libudev-devel libqt5-qtbase-devel libqt5-qtmultimedia-devel libqt5-qtsvg-devel libQt5Gui-private-headers-devel libevdev-devel + sudo zypper install git cmake libasound2 libpulse-devel openal-soft-devel glew-devel zlib-devel libedit-devel vulkan-devel libudev-devel libqt5-qtbase-devel libqt5-qtmultimedia-devel libqt5-qtsvg-devel libQt5Gui-private-headers-devel libevdev-devel libsndio7_1 libjack-devel ## Setup the project diff --git a/CMakeLists.txt b/CMakeLists.txt index c053e05be4..cc0b390063 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,8 +15,6 @@ endif() option(USE_NATIVE_INSTRUCTIONS "USE_NATIVE_INSTRUCTIONS makes rpcs3 compile with -march=native, which is useful for local builds, but not good for packages." ON) 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) @@ -56,12 +54,12 @@ if(MSVC) if(NOT(CMAKE_BUILD_TYPE MATCHES "Release" AND USE_MSVC_STATIC_CRT)) set(USE_DISCORD_RPC OFF CACHE BOOL "Discord RPC is only available in Release and static CRT build." FORCE) endif() - + if(USE_MSVC_STATIC_CRT) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") else() # though doc ( https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html ) - # says if that property is not set then CMake uses the default value MultiThreaded$<$:Debug>DLL + # says if that property is not set then CMake uses the default value MultiThreaded$<$:Debug>DLL # to select a MSVC runtime library. # But yaml-cpp set /MT(d) if CMAKE_MSVC_RUNTIME_LIBRARY is undefined # So we have to define it explicitly @@ -71,10 +69,10 @@ if(MSVC) # TODO(cjj19970505@live.cn) # offical QT uses dynamic CRT. # When building our lib with static CRT and debug build type - # and linking with Qt with dynmaic CRT and debug build, - # error is encountered in runtime (which is expected). - # But building our lib with static CRT and release build type, - # and linking with Qt with dynamic CRT and release build seems to be working, + # and linking with Qt with dynmaic CRT and debug build, + # error is encountered in runtime (which is expected). + # But building our lib with static CRT and release build type, + # and linking with Qt with dynamic CRT and release build seems to be working, # which is the same config with VS solution. # (though technically it might still have some hidden errors). # So we allow static CRT in both relase and debug build, but prompt warning in debug build. diff --git a/Utilities/simple_ringbuf.cpp b/Utilities/simple_ringbuf.cpp new file mode 100644 index 0000000000..ff37b4b295 --- /dev/null +++ b/Utilities/simple_ringbuf.cpp @@ -0,0 +1,96 @@ +#include "Utilities/simple_ringbuf.h" + +simple_ringbuf::simple_ringbuf(u32 size) +{ + set_buf_size(size); +} + +u32 simple_ringbuf::get_free_size() +{ + const u64 _rw_ptr = rw_ptr; + const u32 rd = static_cast(_rw_ptr); + const u32 wr = static_cast(_rw_ptr >> 32); + + return wr >= rd ? buf_size - 1 - (wr - rd) : rd - wr - 1U; +} + +u32 simple_ringbuf::get_used_size() +{ + return buf_size - 1 - get_free_size(); +} + +void simple_ringbuf::set_buf_size(u32 size) +{ + ensure(size); + + this->buf_size = size + 1; + buf = std::make_unique(this->buf_size); + flush(); + initialized = true; +} + +void simple_ringbuf::flush() +{ + rw_ptr.atomic_op([&](u64 &val) + { + val = static_cast(val >> 32) | (val & 0xFFFFFFFF'00000000); + }); +} + +u32 simple_ringbuf::push(const void *data, u32 size) +{ + ensure(data != nullptr && initialized.observe()); + + const u32 old = static_cast(rw_ptr.load() >> 32); + const u32 to_push = std::min(size, get_free_size()); + auto b_data = static_cast(data); + + if (!to_push) return 0; + + if (old + to_push > buf_size) + { + const auto first_write_sz = buf_size - old; + memcpy(&buf[old], b_data, first_write_sz); + memcpy(&buf[0], b_data + first_write_sz, to_push - first_write_sz); + } + else + { + memcpy(&buf[old], b_data, to_push); + } + + rw_ptr.atomic_op([&](u64 &val) + { + val = static_cast((old + to_push) % buf_size) << 32 | static_cast(val); + }); + + return to_push; +} + +u32 simple_ringbuf::pop(void *data, u32 size) +{ + ensure(data != nullptr && initialized.observe()); + + const u32 old = static_cast(rw_ptr.load()); + const u32 to_pop = std::min(size, get_used_size()); + u8 *b_data = static_cast(data); + + if (!to_pop) return 0; + + if (old + to_pop > buf_size) + { + const auto first_read_sz = buf_size - old; + memcpy(b_data, &buf[old], first_read_sz); + memcpy(b_data + first_read_sz, &buf[0], to_pop - first_read_sz); + } + else + { + memcpy(b_data, &buf[old], to_pop); + } + + rw_ptr.atomic_op([&](u64 &val) + { + val = (old + to_pop) % buf_size | (val & 0xFFFFFFFF'00000000); + }); + + return to_pop; +} diff --git a/Utilities/simple_ringbuf.h b/Utilities/simple_ringbuf.h new file mode 100644 index 0000000000..368d1b0e89 --- /dev/null +++ b/Utilities/simple_ringbuf.h @@ -0,0 +1,31 @@ +#pragma once + +#include "util/types.hpp" +#include "util/atomic.hpp" + +// Single reader/writer simple ringbuffer. +// Counters are 32-bit. +class simple_ringbuf +{ +private: + + atomic_t rw_ptr = 0; + u32 buf_size = 0; + std::unique_ptr buf{}; + atomic_t initialized = false; + +public: + + simple_ringbuf() {}; + simple_ringbuf(u32 size); + + u32 get_free_size(); + u32 get_used_size(); + + // Thread unsafe functions. + void set_buf_size(u32 size); + void flush(); // Could be safely called from reader. + + u32 push(const void *data, u32 size); + u32 pop(void *data, u32 size); +}; diff --git a/rpcs3.sln b/rpcs3.sln index dc212942fd..311df85953 100644 --- a/rpcs3.sln +++ b/rpcs3.sln @@ -17,8 +17,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rpcs3.emu", "rpcs3.emu", "{ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XAudio", "rpcs3\XAudio.vcxproj", "{78CB2F39-B809-4A06-8329-8C0A19119D3D}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenAL", "rpcs3\OpenAL.vcxproj", "{30A05C4D-F5FD-421C-A864-17A64BDEAA75}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pnglibconf", "3rdparty\libpng\pnglibconf.vcxproj", "{EB33566E-DA7F-4D28-9077-88C0B7C77E35}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpng", "3rdparty\libpng\libpng.vcxproj", "{D6973076-9317-4EF2-A0B8-B7A18AC0713E}" @@ -47,10 +45,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rpcs3", "rpcs3\rpcs3.vcxpro {C4A10229-4712-4BD2-B63E-50D93C67A038} = {C4A10229-4712-4BD2-B63E-50D93C67A038} {78CB2F39-B809-4A06-8329-8C0A19119D3D} = {78CB2F39-B809-4A06-8329-8C0A19119D3D} {3384223A-6D97-4799-9862-359F85312892} = {3384223A-6D97-4799-9862-359F85312892} - {30A05C4D-F5FD-421C-A864-17A64BDEAA75} = {30A05C4D-F5FD-421C-A864-17A64BDEAA75} {60F89955-91C6-3A36-8000-13C592FEC2DF} = {60F89955-91C6-3A36-8000-13C592FEC2DF} {3EE5F075-B546-42C4-B6A8-E3CCEF38B78D} = {3EE5F075-B546-42C4-B6A8-E3CCEF38B78D} {D6973076-9317-4EF2-A0B8-B7A18AC0713E} = {D6973076-9317-4EF2-A0B8-B7A18AC0713E} + {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} = {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} + {FDA7B080-03B0-48C8-B24F-88118981422A} = {FDA7B080-03B0-48C8-B24F-88118981422A} {DA6F56B4-06A4-441D-AD70-AC5A7D51FADB} = {DA6F56B4-06A4-441D-AD70-AC5A7D51FADB} {FDC361C5-7734-493B-8CFB-037308B35122} = {FDC361C5-7734-493B-8CFB-037308B35122} {8F85B6CC-250F-4ACA-A617-E820A74E3E3C} = {8F85B6CC-250F-4ACA-A617-E820A74E3E3C} @@ -79,6 +78,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3rdParty", "3rdParty", "{6C EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "llvm_build_clang_cl", "llvm_build\llvm_build_clang_cl.vcxproj", "{A37E4273-85DB-4217-B775-CE971B87D9DF}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Cubeb", "rpcs3\Cubeb.vcxproj", "{9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcubeb", "3rdparty\cubeb\libcubeb.vcxproj", "{FDA7B080-03B0-48C8-B24F-88118981422A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -103,10 +106,6 @@ Global {78CB2F39-B809-4A06-8329-8C0A19119D3D}.Debug|x64.Build.0 = Debug|x64 {78CB2F39-B809-4A06-8329-8C0A19119D3D}.Release|x64.ActiveCfg = Release|x64 {78CB2F39-B809-4A06-8329-8C0A19119D3D}.Release|x64.Build.0 = Release|x64 - {30A05C4D-F5FD-421C-A864-17A64BDEAA75}.Debug|x64.ActiveCfg = Debug|x64 - {30A05C4D-F5FD-421C-A864-17A64BDEAA75}.Debug|x64.Build.0 = Debug|x64 - {30A05C4D-F5FD-421C-A864-17A64BDEAA75}.Release|x64.ActiveCfg = Release|x64 - {30A05C4D-F5FD-421C-A864-17A64BDEAA75}.Release|x64.Build.0 = Release|x64 {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug|x64.ActiveCfg = Release|x64 {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug|x64.Build.0 = Release|x64 {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release|x64.ActiveCfg = Release|x64 @@ -161,6 +160,14 @@ Global {4CBD3DDD-5555-49A4-A44D-DD3D8CB516A1}.Release|x64.ActiveCfg = Release|x64 {A37E4273-85DB-4217-B775-CE971B87D9DF}.Debug|x64.ActiveCfg = Debug|x64 {A37E4273-85DB-4217-B775-CE971B87D9DF}.Release|x64.ActiveCfg = Release|x64 + {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}.Debug|x64.ActiveCfg = Debug|x64 + {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}.Debug|x64.Build.0 = Debug|x64 + {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}.Release|x64.ActiveCfg = Release|x64 + {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}.Release|x64.Build.0 = Release|x64 + {FDA7B080-03B0-48C8-B24F-88118981422A}.Debug|x64.ActiveCfg = Debug|x64 + {FDA7B080-03B0-48C8-B24F-88118981422A}.Debug|x64.Build.0 = Debug|x64 + {FDA7B080-03B0-48C8-B24F-88118981422A}.Release|x64.ActiveCfg = Release|x64 + {FDA7B080-03B0-48C8-B24F-88118981422A}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -171,7 +178,6 @@ Global {8BC303AB-25BE-4276-8E57-73F171B2D672} = {B0AC29FD-7B01-4B5E-9C8D-0A081E4C5668} {3384223A-6D97-4799-9862-359F85312892} = {10FBF193-D532-4CCF-B875-4C7091A7F6C2} {78CB2F39-B809-4A06-8329-8C0A19119D3D} = {10FBF193-D532-4CCF-B875-4C7091A7F6C2} - {30A05C4D-F5FD-421C-A864-17A64BDEAA75} = {10FBF193-D532-4CCF-B875-4C7091A7F6C2} {EB33566E-DA7F-4D28-9077-88C0B7C77E35} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} {D6973076-9317-4EF2-A0B8-B7A18AC0713E} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} {60F89955-91C6-3A36-8000-13C592FEC2DF} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} @@ -186,6 +192,8 @@ Global {DA6F56B4-06A4-441D-AD70-AC5A7D51FADB} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} {4CBD3DDD-5555-49A4-A44D-DD3D8CB516A1} = {B0AC29FD-7B01-4B5E-9C8D-0A081E4C5668} {A37E4273-85DB-4217-B775-CE971B87D9DF} = {B0AC29FD-7B01-4B5E-9C8D-0A081E4C5668} + {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} = {10FBF193-D532-4CCF-B875-4C7091A7F6C2} + {FDA7B080-03B0-48C8-B24F-88118981422A} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {06CC7920-E085-4B81-9582-8DE8AAD42510} diff --git a/rpcs3/OpenAL.vcxproj b/rpcs3/Cubeb.vcxproj similarity index 71% rename from rpcs3/OpenAL.vcxproj rename to rpcs3/Cubeb.vcxproj index 3ba5b315d7..47e0bf8a69 100644 --- a/rpcs3/OpenAL.vcxproj +++ b/rpcs3/Cubeb.vcxproj @@ -1,4 +1,4 @@ - + @@ -11,9 +11,8 @@ - {30A05C4D-F5FD-421C-A864-17A64BDEAA75} - Win32Proj - OpenAL + {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} + Cubeb @@ -37,36 +36,28 @@ - - - - - - - - - .\3rdparty\OpenAL\include;%(AdditionalIncludeDirectories) + ..\3rdparty\cubeb\cubeb\include\;..\3rdparty\cubeb\extra\;%(AdditionalIncludeDirectories) MaxSpeed - - - - - - {c4a10229-4712-4bd2-b63e-50d93c67a038} + + + + + + - \ No newline at end of file + diff --git a/rpcs3/OpenAL.vcxproj.filters b/rpcs3/Cubeb.vcxproj.filters similarity index 64% rename from rpcs3/OpenAL.vcxproj.filters rename to rpcs3/Cubeb.vcxproj.filters index 7da7606efa..9c799be1ff 100644 --- a/rpcs3/OpenAL.vcxproj.filters +++ b/rpcs3/Cubeb.vcxproj.filters @@ -1,19 +1,19 @@ - + - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + {F573EFFC-2BB8-43DF-AEFA-4E9EA31A817F} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - Source Files - - - - + Source Files - \ No newline at end of file + + + Source Files + + + diff --git a/rpcs3/Emu/Audio/AL/OpenALBackend.cpp b/rpcs3/Emu/Audio/AL/OpenALBackend.cpp deleted file mode 100644 index 78820c8fd3..0000000000 --- a/rpcs3/Emu/Audio/AL/OpenALBackend.cpp +++ /dev/null @@ -1,212 +0,0 @@ -#include "stdafx.h" - -#include "OpenALBackend.h" - -#ifdef _MSC_VER -#pragma comment(lib, "OpenAL32.lib") -#endif - -LOG_CHANNEL(OpenAL); - -#define checkForAlError(sit) do { ALenum g_last_al_error = alGetError(); if(g_last_al_error != AL_NO_ERROR) OpenAL.error("%s: OpenAL error 0x%04x", sit, g_last_al_error); } while(0) -#define checkForAlcError(sit) do { ALCenum g_last_alc_error = alcGetError(m_device); if(g_last_alc_error != ALC_NO_ERROR) { OpenAL.error("%s: OpenALC error 0x%04x", sit, g_last_alc_error); return; }} while(0) - -OpenALBackend::OpenALBackend() - : AudioBackend() -{ - ALCint attribs[] = {ALC_FREQUENCY, DEFAULT_AUDIO_SAMPLING_RATE, 0, 0}; - - ALCdevice* m_device = alcOpenDevice(nullptr); - checkForAlcError("alcOpenDevice"); - - ALCcontext* m_context = alcCreateContext(m_device, attribs); - checkForAlcError("alcCreateContext"); - - alcMakeContextCurrent(m_context); - checkForAlcError("alcMakeContextCurrent"); - - switch (m_channels) - { - case 2: - m_format = (m_sample_size == 2) ? AL_FORMAT_STEREO16 : AL_FORMAT_STEREO_FLOAT32; - break; - case 6: - m_format = (m_sample_size == 2) ? AL_FORMAT_51CHN16 : AL_FORMAT_51CHN32; - break; - default: - m_format = (m_sample_size == 2) ? AL_FORMAT_71CHN16 : AL_FORMAT_71CHN32; - break; - } -} - -OpenALBackend::~OpenALBackend() -{ - if (alIsSource(m_source)) - { - Close(); - } - - if (ALCcontext* m_context = alcGetCurrentContext()) - { - ALCdevice* m_device = alcGetContextsDevice(m_context); - alcMakeContextCurrent(nullptr); - alcDestroyContext(m_context); - alcCloseDevice(m_device); - } -} - -void OpenALBackend::Play() -{ - AUDIT(alIsSource(m_source)); - - ALint state; - alGetSourcei(m_source, AL_SOURCE_STATE, &state); - checkForAlError("Play->alGetSourcei(AL_SOURCE_STATE)"); - - if (state != AL_PLAYING) - { - alSourcePlay(m_source); - checkForAlError("Play->alSourcePlay"); - } -} - -void OpenALBackend::Pause() -{ - AUDIT(alIsSource(m_source)); - - alSourcePause(m_source); - checkForAlError("Pause->alSourcePause"); -} - -bool OpenALBackend::IsPlaying() -{ - AUDIT(alIsSource(m_source)); - - ALint state; - alGetSourcei(m_source, AL_SOURCE_STATE, &state); - checkForAlError("IsPlaying->alGetSourcei(AL_SOURCE_STATE)"); - - return state == AL_PLAYING; -} - -void OpenALBackend::Open(u32 num_buffers) -{ - AUDIT(!alIsSource(m_source)); - - // Initialize Source - alGenSources(1, &m_source); - checkForAlError("Open->alGenSources"); - - alSourcei(m_source, AL_LOOPING, AL_FALSE); - checkForAlError("Open->alSourcei"); - - // Initialize Buffers - alGenBuffers(num_buffers, m_buffers); - checkForAlError("Open->alGenBuffers"); - - m_num_buffers = num_buffers; - m_num_unqueued = num_buffers; -} - -void OpenALBackend::Close() -{ - if (alIsSource(m_source)) - { - // Stop & Kill Source - Pause(); - alDeleteSources(1, &m_source); - - // Free Buffers - alDeleteBuffers(m_num_buffers, m_buffers); - checkForAlError("alDeleteBuffers"); - } -} - -bool OpenALBackend::AddData(const void* src, u32 num_samples) -{ - AUDIT(alIsSource(m_source)); - - // Unqueue processed buffers, if any - unqueue_processed(); - - // Fail if there are no free buffers remaining - if (m_num_unqueued == 0) - { - OpenAL.warning("No unqueued buffers remaining"); - return false; - } - - // Copy data to the next available buffer - alBufferData(m_buffers[m_next_buffer], m_format, src, num_samples * m_sample_size, m_sampling_rate); - checkForAlError("AddData->alBufferData"); - - // Enqueue buffer - alSourceQueueBuffers(m_source, 1, &m_buffers[m_next_buffer]); - checkForAlError("AddData->alSourceQueueBuffers"); - - m_num_unqueued--; - m_next_buffer = (m_next_buffer + 1) % m_num_buffers; - - return true; -} - -void OpenALBackend::Flush() -{ - AUDIT(alIsSource(m_source)); - - // Stop source first - alSourceStop(m_source); - checkForAlError("Flush->alSourceStop"); - - // Unqueue processed buffers (should now be all of them) - unqueue_processed(); -} - -void OpenALBackend::unqueue_processed() -{ - AUDIT(alIsSource(m_source)); - - // Get number of buffers - ALint num_processed; - alGetSourcei(m_source, AL_BUFFERS_PROCESSED, &num_processed); - checkForAlError("Flush->alGetSourcei(AL_BUFFERS_PROCESSED)"); - - if (num_processed > 0) - { - // Unqueue all buffers - ALuint x[MAX_AUDIO_BUFFERS]; - alSourceUnqueueBuffers(m_source, num_processed, x); - checkForAlError("Flush->alSourceUnqueueBuffers"); - - m_num_unqueued += num_processed; - } -} - -u64 OpenALBackend::GetNumEnqueuedSamples() -{ - AUDIT(alIsSource(m_source)); - - // Get number of buffers queued - ALint num_queued; - alGetSourcei(m_source, AL_BUFFERS_QUEUED, &num_queued); - checkForAlError("GetNumEnqueuedSamples->alGetSourcei(AL_BUFFERS_QUEUED)"); - AUDIT(static_cast(num_queued) <= m_num_buffers - m_num_unqueued); - - // Get sample position - ALint sample_pos; - alGetSourcei(m_source, AL_SAMPLE_OFFSET, &sample_pos); - checkForAlError("GetNumEnqueuedSamples->alGetSourcei(AL_SAMPLE_OFFSET)"); - - // Return - return (num_queued * AUDIO_BUFFER_SAMPLES) + (sample_pos % AUDIO_BUFFER_SAMPLES); -} - -f32 OpenALBackend::SetFrequencyRatio(f32 new_ratio) -{ - new_ratio = std::clamp(new_ratio, 0.5f, 2.0f); - - alSourcef(m_source, AL_PITCH, new_ratio); - checkForAlError("SetFrequencyRatio->alSourcei(AL_PITCH)"); - - return new_ratio; -} \ No newline at end of file diff --git a/rpcs3/Emu/Audio/AL/OpenALBackend.h b/rpcs3/Emu/Audio/AL/OpenALBackend.h deleted file mode 100644 index ef077f729a..0000000000 --- a/rpcs3/Emu/Audio/AL/OpenALBackend.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "Emu/Audio/AudioBackend.h" -#include "3rdparty/OpenAL/include/alext.h" - -class OpenALBackend : public AudioBackend -{ -private: - ALint m_format{}; - ALuint m_source{}; - ALuint m_buffers[MAX_AUDIO_BUFFERS]{}; - ALsizei m_num_buffers{}; - - u32 m_next_buffer = 0; - u32 m_num_unqueued = 0; - - void unqueue_processed(); - -public: - OpenALBackend(); - ~OpenALBackend() override; - - const char* GetName() const override { return "OpenAL"; } - - static const u32 capabilities = PLAY_PAUSE_FLUSH | IS_PLAYING | GET_NUM_ENQUEUED_SAMPLES | SET_FREQUENCY_RATIO; - u32 GetCapabilities() const override { return capabilities; } - - void Open(u32 num_buffers) override; - void Close() override; - - void Play() override; - void Pause() override; - bool IsPlaying() override; - - bool AddData(const void* src, u32 num_samples) override; - void Flush() override; - - u64 GetNumEnqueuedSamples() override; - f32 SetFrequencyRatio(f32 new_ratio) override; -}; diff --git a/rpcs3/Emu/Audio/ALSA/ALSABackend.cpp b/rpcs3/Emu/Audio/ALSA/ALSABackend.cpp deleted file mode 100644 index 51f74bc7c6..0000000000 --- a/rpcs3/Emu/Audio/ALSA/ALSABackend.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#ifndef HAVE_ALSA -#error "ALSA support disabled but still being built." -#endif - -#include "stdafx.h" -#include "Emu/system_config.h" - -#include "ALSABackend.h" - -LOG_CHANNEL(ALSA); - -static void error(int err, const char* reason) -{ - ALSA.error("ALSA: %s failed: %s\n", reason, snd_strerror(err)); -} - -static bool check(int err, const char* reason) -{ - if (err < 0) - { - error(err, reason); - return false; - } - - return true; -} - -ALSABackend::ALSABackend() - : AudioBackend() -{ -} - -ALSABackend::~ALSABackend() -{ - if (tls_sw_params || tls_hw_params || tls_handle) - { - Close(); - } -} - -void ALSABackend::Open(u32 num_buffers) -{ - if (!check(snd_pcm_open(&tls_handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK), "snd_pcm_open")) - return; - - if (!check(snd_pcm_hw_params_malloc(&tls_hw_params), "snd_pcm_hw_params_malloc")) - return; - - if (!check(snd_pcm_hw_params_any(tls_handle, tls_hw_params), "snd_pcm_hw_params_any")) - return; - - if (!check(snd_pcm_hw_params_set_access(tls_handle, tls_hw_params, SND_PCM_ACCESS_RW_INTERLEAVED), "snd_pcm_hw_params_set_access")) - return; - - if (!check(snd_pcm_hw_params_set_format(tls_handle, tls_hw_params, m_convert_to_u16 ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_FLOAT_LE), "snd_pcm_hw_params_set_format")) - return; - - uint rate = m_sampling_rate; - if (!check(snd_pcm_hw_params_set_rate_near(tls_handle, tls_hw_params, &rate, nullptr), "snd_pcm_hw_params_set_rate_near")) - return; - - if (!check(snd_pcm_hw_params_set_channels(tls_handle, tls_hw_params, m_channels), "snd_pcm_hw_params_set_channels")) - return; - - //uint period = 5333; - //if (!check(snd_pcm_hw_params_set_period_time_near(tls_handle, tls_hw_params, &period, nullptr), "snd_pcm_hw_params_set_period_time_near")) - // return; - - //if (!check(snd_pcm_hw_params_set_periods(tls_handle, tls_hw_params, 4, 0), "snd_pcm_hw_params_set_periods")) - // return; - - snd_pcm_uframes_t bufsize_frames = num_buffers * AUDIO_BUFFER_SAMPLES; - snd_pcm_uframes_t period_frames = AUDIO_BUFFER_SAMPLES; - - if (!check(snd_pcm_hw_params_set_buffer_size_near(tls_handle, tls_hw_params, &bufsize_frames), "snd_pcm_hw_params_set_buffer_size_near")) - return; - - if (!check(snd_pcm_hw_params_set_period_size_near(tls_handle, tls_hw_params, &period_frames, 0), "snd_pcm_hw_params_set_period_size")) - return; - - if (!check(snd_pcm_hw_params(tls_handle, tls_hw_params), "snd_pcm_hw_params")) - return; - - if (!check(snd_pcm_hw_params_get_buffer_size(tls_hw_params, &bufsize_frames), "snd_pcm_hw_params_get_buffer_size")) - return; - - if (!check(snd_pcm_hw_params_get_period_size(tls_hw_params, &period_frames, nullptr), "snd_pcm_hw_params_get_period_size")) - return; - - if (!check(snd_pcm_sw_params_malloc(&tls_sw_params), "snd_pcm_sw_params_malloc")) - return; - - if (!check(snd_pcm_sw_params_current(tls_handle, tls_sw_params), "snd_pcm_sw_params_current")) - return; - - period_frames *= m_start_threshold; - - if (!check(snd_pcm_sw_params_set_start_threshold(tls_handle, tls_sw_params, period_frames + 1), "snd_pcm_sw_params_set_start_threshold")) - return; - - if (!check(snd_pcm_sw_params_set_stop_threshold(tls_handle, tls_sw_params, bufsize_frames), "snd_pcm_sw_params_set_stop_threshold")) - return; - - if (!check(snd_pcm_sw_params(tls_handle, tls_sw_params), "snd_pcm_sw_params")) - return; - - if (!check(snd_pcm_prepare(tls_handle), "snd_pcm_prepare")) - return; - - ALSA.notice("bufsize_frames=%u, period_frames=%u", bufsize_frames, period_frames); -} - -void ALSABackend::Close() -{ - if (tls_sw_params) - { - snd_pcm_sw_params_free(tls_sw_params); - tls_sw_params = nullptr; - } - - if (tls_hw_params) - { - snd_pcm_hw_params_free(tls_hw_params); - tls_hw_params = nullptr; - } - - if (tls_handle) - { - snd_pcm_close(tls_handle); - tls_handle = nullptr; - } -} - -bool ALSABackend::AddData(const void* src, u32 num_samples) -{ - u32 num_frames = num_samples / m_channels; - - int res = snd_pcm_writei(tls_handle, src, num_frames); - - if (res == -EAGAIN) - { - ALSA.warning("EAGAIN"); - return false; - } - - if (res < 0) - { - res = snd_pcm_recover(tls_handle, res, 0); - - if (res < 0) - { - ALSA.warning("Failed to recover (%d)", res); - return false; - } - - return false; - } - - if (res + 0u != num_frames) - { - ALSA.warning("Error (%d)", res); - return false; - } - - return true; -} diff --git a/rpcs3/Emu/Audio/ALSA/ALSABackend.h b/rpcs3/Emu/Audio/ALSA/ALSABackend.h deleted file mode 100644 index 5da05b807b..0000000000 --- a/rpcs3/Emu/Audio/ALSA/ALSABackend.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#ifndef HAVE_ALSA -#error "ALSA support disabled but still being built." -#endif - -#include "Emu/Audio/AudioBackend.h" - -#include - -class ALSABackend : public AudioBackend -{ - snd_pcm_t* tls_handle{}; - snd_pcm_hw_params_t* tls_hw_params{}; - snd_pcm_sw_params_t* tls_sw_params{}; - -public: - ALSABackend(); - virtual ~ALSABackend() override; - - ALSABackend(const ALSABackend&) = delete; - ALSABackend& operator=(const ALSABackend&) = delete; - - virtual const char* GetName() const override { return "ALSA"; } - - static const u32 capabilities = 0; - virtual u32 GetCapabilities() const override { return capabilities; } - - virtual void Open(u32) override; - virtual void Close() override; - - virtual bool AddData(const void* src, u32 num_samples) override; -}; diff --git a/rpcs3/Emu/Audio/AudioBackend.cpp b/rpcs3/Emu/Audio/AudioBackend.cpp index 65add4fb22..d2edb2a822 100644 --- a/rpcs3/Emu/Audio/AudioBackend.cpp +++ b/rpcs3/Emu/Audio/AudioBackend.cpp @@ -2,67 +2,29 @@ #include "AudioBackend.h" #include "Emu/system_config.h" -AudioBackend::AudioBackend() -{ - m_convert_to_u16 = static_cast(g_cfg.audio.convert_to_u16); - m_sample_size = m_convert_to_u16 ? sizeof(u16) : sizeof(float); - m_start_threshold = g_cfg.audio.start_threshold; - - const u32 sampling_period_multiplier_u32 = g_cfg.audio.sampling_period_multiplier; - - if (sampling_period_multiplier_u32 == 100) - { - m_sampling_rate = DEFAULT_AUDIO_SAMPLING_RATE; - } - else - { - const f32 sampling_period_multiplier = sampling_period_multiplier_u32 / 100.0f; - const f32 sampling_rate_multiplier = 1.0f / sampling_period_multiplier; - m_sampling_rate = static_cast(f32{ DEFAULT_AUDIO_SAMPLING_RATE } *sampling_rate_multiplier); - } - - const audio_downmix downmix = g_cfg.audio.audio_channel_downmix.get(); - - switch (downmix) - { - case audio_downmix::no_downmix: - m_channels = 8; - break; - case audio_downmix::downmix_to_stereo: - m_channels = 2; - break; - case audio_downmix::downmix_to_5_1: - m_channels = 6; - break; - case audio_downmix::use_application_settings: - m_channels = 2; // TODO - break; - default: - fmt::throw_exception("Unknown audio channel mode %s (%d)", downmix, static_cast(downmix)); - } -} +AudioBackend::AudioBackend() {} /* * Helper methods */ u32 AudioBackend::get_sampling_rate() const { - return m_sampling_rate; + return static_cast>(m_sampling_rate); } u32 AudioBackend::get_sample_size() const { - return m_sample_size; + return static_cast>(m_sample_size); } u32 AudioBackend::get_channels() const { - return m_channels; + return static_cast>(m_channels); } -bool AudioBackend::get_convert_to_u16() const +bool AudioBackend::get_convert_to_s16() const { - return m_convert_to_u16; + return m_sample_size == AudioSampleSize::S16; } bool AudioBackend::has_capability(u32 cap) const @@ -75,24 +37,6 @@ void AudioBackend::dump_capabilities(std::string& out) const u32 count = 0; const u32 capabilities = GetCapabilities(); - if (capabilities & PLAY_PAUSE_FLUSH) - { - fmt::append(out, "PLAY_PAUSE_FLUSH"); - count++; - } - - if (capabilities & IS_PLAYING) - { - fmt::append(out, "%sIS_PLAYING", count > 0 ? " | " : ""); - count++; - } - - if (capabilities & GET_NUM_ENQUEUED_SAMPLES) - { - fmt::append(out, "%sGET_NUM_ENQUEUED_SAMPLES", count > 0 ? " | " : ""); - count++; - } - if (capabilities & SET_FREQUENCY_RATIO) { fmt::append(out, "%sSET_FREQUENCY_RATIO", count > 0 ? " | " : ""); diff --git a/rpcs3/Emu/Audio/AudioBackend.h b/rpcs3/Emu/Audio/AudioBackend.h index c5b7aeff9c..0cffbf4f24 100644 --- a/rpcs3/Emu/Audio/AudioBackend.h +++ b/rpcs3/Emu/Audio/AudioBackend.h @@ -7,7 +7,33 @@ enum : u32 { DEFAULT_AUDIO_SAMPLING_RATE = 48000, MAX_AUDIO_BUFFERS = 64, - AUDIO_BUFFER_SAMPLES = 256 + AUDIO_BUFFER_SAMPLES = 256, + AUDIO_MIN_LATENCY = 512, + AUDIO_MAX_CHANNELS = 8, +}; + +enum class AudioFreq : u32 +{ + FREQ_32K = 32000, + FREQ_44K = 44100, + FREQ_48K = 48000, + FREQ_88K = 88200, + FREQ_96K = 96000, + FREQ_176K = 176400, + FREQ_192K = 192000, +}; + +enum class AudioSampleSize : u32 +{ + FLOAT = sizeof(float), + S16 = sizeof(s16), +}; + +enum class AudioChannelCnt : u32 +{ + STEREO = 2, + SURROUND_5_1 = 6, + SURROUND_7_1 = 8, }; class AudioBackend @@ -15,10 +41,7 @@ class AudioBackend public: enum Capabilities : u32 { - PLAY_PAUSE_FLUSH = 0x1, // Implements Play, Pause, Flush - IS_PLAYING = 0x2, // Implements IsPlaying - GET_NUM_ENQUEUED_SAMPLES = 0x4, // Implements GetNumEnqueuedSamples - SET_FREQUENCY_RATIO = 0x8, // Implements SetFrequencyRatio + SET_FREQUENCY_RATIO = 0x1, // Implements SetFrequencyRatio }; AudioBackend(); @@ -31,59 +54,40 @@ public: virtual const char* GetName() const = 0; virtual u32 GetCapabilities() const = 0; - virtual void Open(u32 num_buffers) = 0; + virtual void Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) = 0; virtual void Close() = 0; - virtual bool AddData(const void* src, u32 num_samples) = 0; + // Sets write callback. It's called when backend requests new data to be sent + // Callback should return number of submitted bytes. Calling other backend functions from callback is unsafe + virtual void SetWriteCallback(std::function cb) = 0; + // Returns length of one callback frame in seconds. + virtual f64 GetCallbackFrameLen() = 0; + + // Returns true if audio is currently being played, false otherwise + virtual bool IsPlaying() = 0; + + // Start playing enqueued data + virtual void Play() = 0; + + // Pause playing enqueued data + virtual void Pause() = 0; /* * This virtual method should be reimplemented if backend can fail to be initialized under non-error conditions * eg. when there is no audio devices attached */ - virtual bool Initialized() const { return true; } + virtual bool Initialized() { return true; } + + /* + * This virtual method should be reimplemented if backend can fail during normal operation + */ + virtual bool Operational() { return true; } /* * Virtual methods - should be implemented depending on backend capabilities */ - // Start playing enqueued data - // Should be implemented if capabilities & PLAY_PAUSE_FLUSH - virtual void Play() - { - fmt::throw_exception("Play() not implemented"); - } - - // Pause playing enqueued data - // Should be implemented if capabilities & PLAY_PAUSE_FLUSH - virtual void Pause() - { - fmt::throw_exception("Pause() not implemented"); - } - - // Pause audio, and unqueue all currently queued buffers - // Should be implemented if capabilities & PLAY_PAUSE_FLUSH - virtual void Flush() - { - - fmt::throw_exception("Flush() not implemented"); - } - - // Returns true if audio is currently being played, false otherwise - // Should be implemented if capabilities & IS_PLAYING - virtual bool IsPlaying() - { - fmt::throw_exception("IsPlaying() not implemented"); - } - - // Returns the number of currently enqueued samples - // Should be implemented if capabilities & GET_NUM_ENQUEUED_SAMPLES - virtual u64 GetNumEnqueuedSamples() - { - fmt::throw_exception("GetNumEnqueuedSamples() not implemented"); - return 0; - } - // Sets a new frequency ratio. Backend is allowed to modify the ratio value, e.g. clamping it to the allowed range // Returns the new frequency ratio set // Should be implemented if capabilities & SET_FREQUENCY_RATIO @@ -93,7 +97,6 @@ public: return 1.0f; } - /* * Helper methods */ @@ -103,16 +106,14 @@ public: u32 get_channels() const; - bool get_convert_to_u16() const; + bool get_convert_to_s16() const; bool has_capability(u32 cap) const; void dump_capabilities(std::string& out) const; protected: - bool m_convert_to_u16 = false; - u32 m_sample_size = sizeof(float); - u32 m_sampling_rate = DEFAULT_AUDIO_SAMPLING_RATE; - u32 m_channels = 0; - u32 m_start_threshold = 1; + AudioSampleSize m_sample_size = AudioSampleSize::FLOAT; + AudioFreq m_sampling_rate = AudioFreq::FREQ_48K; + AudioChannelCnt m_channels = AudioChannelCnt::STEREO; }; diff --git a/rpcs3/Emu/Audio/AudioDumper.cpp b/rpcs3/Emu/Audio/AudioDumper.cpp index 8082bff347..1b3a4e3cf9 100644 --- a/rpcs3/Emu/Audio/AudioDumper.cpp +++ b/rpcs3/Emu/Audio/AudioDumper.cpp @@ -4,8 +4,8 @@ #include "Utilities/date_time.h" #include "Emu/System.h" -AudioDumper::AudioDumper(u16 ch) - : m_header(ch) +AudioDumper::AudioDumper(u16 ch, u32 sample_rate, u32 sample_size) + : m_header(ch, sample_rate, sample_size) { if (GetCh()) { diff --git a/rpcs3/Emu/Audio/AudioDumper.h b/rpcs3/Emu/Audio/AudioDumper.h index c8fd92e43c..44ada0f4bd 100644 --- a/rpcs3/Emu/Audio/AudioDumper.h +++ b/rpcs3/Emu/Audio/AudioDumper.h @@ -30,19 +30,19 @@ struct WAVHeader u32 SampleRate; // 48000 u32 ByteRate; // SampleRate * NumChannels * BitsPerSample/8 u16 BlockAlign; // NumChannels * BitsPerSample/8 - u16 BitsPerSample; // sizeof(float) * 8 + u16 BitsPerSample; // SampleSize * 8 FMTHeader() = default; - FMTHeader(u16 ch) + FMTHeader(u16 ch, u32 sample_rate, u32 sample_size) : ID("fmt "_u32) , Size(16) - , AudioFormat(3) + , AudioFormat(sample_size == sizeof(float) ? 3 : 1) , NumChannels(ch) - , SampleRate(48000) - , ByteRate(SampleRate * ch * sizeof(float)) - , BlockAlign(ch * sizeof(float)) - , BitsPerSample(sizeof(float) * 8) + , SampleRate(sample_rate) + , ByteRate(SampleRate * ch * sample_size) + , BlockAlign(ch * sample_size) + , BitsPerSample(sample_size * 8) { } } FMT; @@ -52,9 +52,9 @@ struct WAVHeader WAVHeader() = default; - WAVHeader(u16 ch) + WAVHeader(u16 ch, u32 sample_rate, u32 sample_size) : RIFF(sizeof(RIFFHeader) + sizeof(FMTHeader)) - , FMT(ch) + , FMT(ch, sample_rate, sample_size) , ID("data"_u32) , Size(0) { @@ -67,7 +67,7 @@ class AudioDumper fs::file m_output{}; public: - AudioDumper(u16 ch); + AudioDumper(u16 ch, u32 sample_rate, u32 sample_size); ~AudioDumper(); void WriteData(const void* buffer, u32 size); diff --git a/rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp b/rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp new file mode 100644 index 0000000000..c6e90ae417 --- /dev/null +++ b/rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp @@ -0,0 +1,215 @@ +#include "Emu/Audio/Cubeb/CubebBackend.h" + +#include +#include "util/logs.hpp" + +#ifdef _WIN32 +#include +#include +#endif + +LOG_CHANNEL(Cubeb); + +CubebBackend::CubebBackend() + : AudioBackend() +{ +#ifdef _WIN32 + // Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (SUCCEEDED(hr)) + { + m_com_init_success = true; + } +#endif + + if (int err = cubeb_init(&m_ctx, "RPCS3", nullptr)) + { + Cubeb.error("cubeb_init() failed: 0x%08x", err); + m_ctx = nullptr; + return; + } + + Cubeb.notice("Using backend %s", cubeb_get_backend_id(m_ctx)); +} + +CubebBackend::~CubebBackend() +{ + Close(); + + if (m_ctx) + { + cubeb_destroy(m_ctx); + } + +#ifdef _WIN32 + if (m_com_init_success) + { + CoUninitialize(); + } +#endif +} + +void CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) +{ + if (m_ctx == nullptr) return; + + std::lock_guard lock(m_cb_mutex); + CloseUnlocked(); + + m_sampling_rate = freq; + m_sample_size = sample_size; + m_channels = ch_cnt; + + cubeb_stream_params stream_param{}; + stream_param.format = get_convert_to_s16() ? CUBEB_SAMPLE_S16NE : CUBEB_SAMPLE_FLOAT32NE; + stream_param.rate = get_sampling_rate(); + stream_param.channels = get_channels(); + stream_param.layout = [&]() + { + switch (ch_cnt) + { + case AudioChannelCnt::STEREO: return CUBEB_LAYOUT_STEREO; + case AudioChannelCnt::SURROUND_5_1: return CUBEB_LAYOUT_3F2_LFE; + case AudioChannelCnt::SURROUND_7_1: return CUBEB_LAYOUT_3F4_LFE; + default: + ensure(false); + return CUBEB_LAYOUT_UNDEFINED; + } + }(); + + u32 min_latency{}; + if (int err = cubeb_get_min_latency(m_ctx, &stream_param, &min_latency)) + { + Cubeb.error("cubeb_get_min_latency() failed: 0x%08x", err); + } + + if (int err = cubeb_stream_init(m_ctx, &m_stream, "Main stream", nullptr, nullptr, nullptr, &stream_param, std::max(AUDIO_MIN_LATENCY, min_latency), data_cb, state_cb, this)) + { + m_stream = nullptr; + Cubeb.error("cubeb_stream_init() failed: 0x%08x", err); + return; + } + + if (int err = cubeb_stream_set_volume(m_stream, 1.0)) + { + Cubeb.error("cubeb_stream_set_volume() failed: 0x%08x", err); + } +} + +void CubebBackend::CloseUnlocked() +{ + if (m_stream == nullptr) return; + + if (int err = cubeb_stream_stop(m_stream)) + { + Cubeb.error("cubeb_stream_stop() failed: 0x%08x", err); + } + + cubeb_stream_destroy(m_stream); + + m_playing = false; + m_stream = nullptr; + memset(m_last_sample, 0, sizeof(m_last_sample)); +} + +void CubebBackend::Close() +{ + std::lock_guard lock(m_cb_mutex); + CloseUnlocked(); +} + +void CubebBackend::Play() +{ + ensure(m_stream != nullptr); + + if (m_playing) return; + + if (int err = cubeb_stream_start(m_stream)) + { + Cubeb.error("cubeb_stream_start() failed: 0x%08x", err); + } + + std::lock_guard lock(m_cb_mutex); + m_playing = true; +} + +void CubebBackend::Pause() +{ + ensure(m_stream != nullptr); + + if (int err = cubeb_stream_stop(m_stream)) + { + Cubeb.error("cubeb_stream_stop() failed: 0x%08x", err); + } + + std::lock_guard lock(m_cb_mutex); + m_playing = false; +} + +bool CubebBackend::IsPlaying() +{ + ensure(m_stream != nullptr); + + return m_playing; +} + +void CubebBackend::SetWriteCallback(std::function cb) +{ + std::lock_guard lock(m_cb_mutex); + m_write_callback = cb; +} + +f64 CubebBackend::GetCallbackFrameLen() +{ + ensure(m_stream != nullptr); + + cubeb_stream_params stream_param{}; + stream_param.format = get_convert_to_s16() ? CUBEB_SAMPLE_S16NE : CUBEB_SAMPLE_FLOAT32NE; + stream_param.rate = get_sampling_rate(); + stream_param.channels = get_channels(); + + u32 min_latency{}; + if (int err = cubeb_get_min_latency(m_ctx, &stream_param, &min_latency)) + { + Cubeb.error("cubeb_get_min_latency() failed: 0x%08x", err); + } + + return static_cast(std::max(AUDIO_MIN_LATENCY, min_latency)) / get_sampling_rate(); +} + +long CubebBackend::data_cb(cubeb_stream* /* stream */, void* user_ptr, void const* /* input_buffer */, void* output_buffer, long nframes) +{ + CubebBackend* const cubeb = static_cast(user_ptr); + std::unique_lock lock(cubeb->m_cb_mutex, std::defer_lock); + + if (nframes && lock.try_lock() && cubeb->m_write_callback && cubeb->m_playing) + { + const u32 sample_size = cubeb->get_sample_size() * cubeb->get_channels(); + const u32 bytes_req = nframes * cubeb->get_sample_size() * cubeb->get_channels(); + u32 written = std::min(cubeb->m_write_callback(bytes_req, output_buffer), bytes_req); + written -= written % sample_size; + + if (written >= sample_size) + { + memcpy(cubeb->m_last_sample, static_cast(output_buffer) + written - sample_size, sample_size); + } + + for (u32 i = written; i < bytes_req; i += sample_size) + { + memcpy(static_cast(output_buffer) + i, cubeb->m_last_sample, sample_size); + } + } + + return nframes; +} + +void CubebBackend::state_cb(cubeb_stream* /* stream */, void* user_ptr, cubeb_state state) +{ + CubebBackend* const cubeb = static_cast(user_ptr); + + if (state == CUBEB_STATE_ERROR) + { + Cubeb.error("Stream entered error state"); + cubeb->m_reset_req = true; + } +} diff --git a/rpcs3/Emu/Audio/Cubeb/CubebBackend.h b/rpcs3/Emu/Audio/Cubeb/CubebBackend.h new file mode 100644 index 0000000000..c98e36ba19 --- /dev/null +++ b/rpcs3/Emu/Audio/Cubeb/CubebBackend.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include "Utilities/mutex.h" +#include "util/atomic.hpp" +#include "Emu/Audio/AudioBackend.h" + +#include "cubeb/cubeb.h" + +class CubebBackend final : public AudioBackend +{ +public: + CubebBackend(); + ~CubebBackend() override; + + CubebBackend(const CubebBackend&) = delete; + CubebBackend& operator=(const CubebBackend&) = delete; + + const char* GetName() const override { return "Cubeb"; } + + static const u32 capabilities = 0; + u32 GetCapabilities() const override { return capabilities; } + + bool Initialized() override { return m_ctx != nullptr; } + bool Operational() override { return m_ctx != nullptr && m_stream != nullptr && !m_reset_req.observe(); } + + void Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; + void Close() override; + + void SetWriteCallback(std::function cb) override; + f64 GetCallbackFrameLen() override; + + void Play() override; + void Pause() override; + bool IsPlaying() override; + +private: + cubeb *m_ctx = nullptr; + cubeb_stream *m_stream = nullptr; +#ifdef _WIN32 + bool m_com_init_success = false; +#endif + + shared_mutex m_cb_mutex{}; + std::function m_write_callback{}; + u8 m_last_sample[sizeof(float) * static_cast(AudioChannelCnt::SURROUND_7_1)]{}; + + bool m_playing = false; + atomic_t m_reset_req = false; + + // Cubeb callbacks + static long data_cb(cubeb_stream* stream, void* user_ptr, void const* input_buffer, void* output_buffer, long nframes); + static void state_cb(cubeb_stream* stream, void* user_ptr, cubeb_state state); + + void CloseUnlocked(); +}; diff --git a/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp b/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp index 16110f0b67..9d529990a2 100644 --- a/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp +++ b/rpcs3/Emu/Audio/FAudio/FAudioBackend.cpp @@ -12,28 +12,41 @@ LOG_CHANNEL(FAudio_, "FAudio"); FAudioBackend::FAudioBackend() : AudioBackend() { - u32 res = FAudioCreate(&m_instance, 0, FAUDIO_DEFAULT_PROCESSOR); + FAudio *instance; + + u32 res = FAudioCreate(&instance, 0, FAUDIO_DEFAULT_PROCESSOR); if (res) { - FAudio_.fatal("FAudioCreate() failed(0x%08x)", res); + FAudio_.error("FAudioCreate() failed(0x%08x)", res); return; } - res = FAudio_CreateMasteringVoice(m_instance, &m_master_voice, m_channels, 48000, 0, 0, nullptr); + res = FAudio_CreateMasteringVoice(instance, &m_master_voice, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, 0, nullptr); if (res) { - FAudio_.fatal("FAudio_CreateMasteringVoice() failed(0x%08x)", res); + FAudio_.error("FAudio_CreateMasteringVoice() failed(0x%08x)", res); + FAudio_StopEngine(instance); return; } + + OnProcessingPassStart = nullptr; + OnProcessingPassEnd = nullptr; + OnCriticalError = OnCriticalError_func; + + res = FAudio_RegisterForCallbacks(instance, this); + if (res) + { + // Some error recovery functionality will be lost, but otherwise backend is operational + FAudio_.error("FAudio_RegisterForCallbacks() failed(0x%08x)", res); + } + + // All succeeded, "commit" + m_instance = instance; } FAudioBackend::~FAudioBackend() { - if (m_source_voice != nullptr) - { - FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW); - FAudioVoice_DestroyVoice(m_source_voice); - } + Close(); if (m_master_voice != nullptr) { @@ -49,127 +62,192 @@ FAudioBackend::~FAudioBackend() void FAudioBackend::Play() { - AUDIT(m_source_voice != nullptr); + ensure(m_source_voice != nullptr); + + if (m_playing) return; const u32 res = FAudioSourceVoice_Start(m_source_voice, 0, FAUDIO_COMMIT_NOW); if (res) { - FAudio_.fatal("FAudioSourceVoice_Start() failed(0x%08x)", res); + FAudio_.error("FAudioSourceVoice_Start() failed(0x%08x)", res); } + + std::lock_guard lock(m_cb_mutex); + m_playing = true; } void FAudioBackend::Pause() { - AUDIT(m_source_voice != nullptr); + ensure(m_source_voice != nullptr); + + u32 res = FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW); + if (res) + { + FAudio_.error("FAudioSourceVoice_Stop() failed(0x%08x)", res); + } + + res = FAudioSourceVoice_FlushSourceBuffers(m_source_voice); + if (res) + { + FAudio_.error("FAudioSourceVoice_FlushSourceBuffers() failed(0x%08x)", res); + } + + std::lock_guard lock(m_cb_mutex); + m_playing = false; +} + +void FAudioBackend::CloseUnlocked() +{ + if (m_source_voice == nullptr) return; const u32 res = FAudioSourceVoice_Stop(m_source_voice, 0, FAUDIO_COMMIT_NOW); if (res) { - FAudio_.fatal("FAudioSourceVoice_Stop() failed(0x%08x)", res); + FAudio_.error("FAudioSourceVoice_Stop() failed(0x%08x)", res); } -} -void FAudioBackend::Flush() -{ - AUDIT(m_source_voice != nullptr); - - const u32 res = FAudioSourceVoice_FlushSourceBuffers(m_source_voice); - if (res) - { - FAudio_.fatal("FAudioSourceVoice_FlushSourceBuffers() failed(0x%08x)", res); - } -} - -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; + FAudioVoice_DestroyVoice(m_source_voice); + m_playing = false; + m_source_voice = nullptr; + m_data_buf = nullptr; + m_data_buf_len = 0; + memset(m_last_sample, 0, sizeof(m_last_sample)); } void FAudioBackend::Close() { - Pause(); - Flush(); + std::lock_guard lock(m_cb_mutex); + CloseUnlocked(); } -void FAudioBackend::Open(u32 /* num_buffers */) +void FAudioBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) { + if (m_instance == nullptr) return; + + std::lock_guard lock(m_cb_mutex); + CloseUnlocked(); + + m_sampling_rate = freq; + m_sample_size = sample_size; + m_channels = ch_cnt; + FAudioWaveFormatEx waveformatex; - waveformatex.wFormatTag = m_convert_to_u16 ? FAUDIO_FORMAT_PCM : FAUDIO_FORMAT_IEEE_FLOAT; - waveformatex.nChannels = m_channels; - waveformatex.nSamplesPerSec = m_sampling_rate; - waveformatex.nAvgBytesPerSec = static_cast(m_sampling_rate * m_channels * m_sample_size); - waveformatex.nBlockAlign = m_channels * m_sample_size; - waveformatex.wBitsPerSample = m_sample_size * 8; - waveformatex.cbSize = 0; + waveformatex.wFormatTag = get_convert_to_s16() ? FAUDIO_FORMAT_PCM : FAUDIO_FORMAT_IEEE_FLOAT; + waveformatex.nChannels = get_channels(); + waveformatex.nSamplesPerSec = get_sampling_rate(); + waveformatex.nAvgBytesPerSec = static_cast(get_sampling_rate() * get_channels() * get_sample_size()); + waveformatex.nBlockAlign = get_channels() * get_sample_size(); + waveformatex.wBitsPerSample = get_sample_size() * 8; + waveformatex.cbSize = 0; - const u32 res = FAudio_CreateSourceVoice(m_instance, &m_source_voice, &waveformatex, 0, FAUDIO_DEFAULT_FREQ_RATIO, nullptr, nullptr, nullptr); + OnVoiceProcessingPassStart = OnVoiceProcessingPassStart_func; + OnVoiceProcessingPassEnd = nullptr; + OnStreamEnd = nullptr; + OnBufferStart = nullptr; + OnBufferEnd = nullptr; + OnLoopEnd = nullptr; + OnVoiceError = nullptr; + + const u32 res = FAudio_CreateSourceVoice(m_instance, &m_source_voice, &waveformatex, 0, FAUDIO_DEFAULT_FREQ_RATIO, this, nullptr, nullptr); if (res) { - FAudio_.fatal("FAudio_CreateSourceVoice() failed(0x%08x)", res); + FAudio_.error("FAudio_CreateSourceVoice() failed(0x%08x)", res); } - AUDIT(m_source_voice != nullptr); + ensure(m_source_voice != nullptr); FAudioVoice_SetVolume(m_source_voice, 1.0f, FAUDIO_COMMIT_NOW); + + m_data_buf_len = get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast(FAUDIO_DEFAULT_FREQ_RATIO) / 1000; + m_data_buf = std::make_unique(m_data_buf_len); } -bool FAudioBackend::AddData(const void* src, u32 num_samples) +bool FAudioBackend::IsPlaying() { - AUDIT(m_source_voice != nullptr); + ensure(m_source_voice != nullptr); - FAudioVoiceState state; - FAudioSourceVoice_GetState(m_source_voice, &state, FAUDIO_VOICE_NOSAMPLESPLAYED); - - if (state.BuffersQueued >= MAX_AUDIO_BUFFERS) - { - FAudio_.warning("Too many buffers enqueued (%d)", state.BuffersQueued); - return false; - } - - FAudioBuffer buffer; - buffer.AudioBytes = num_samples * m_sample_size; - buffer.Flags = 0; - buffer.LoopBegin = FAUDIO_NO_LOOP_REGION; - buffer.LoopCount = 0; - buffer.LoopLength = 0; - buffer.pAudioData = static_cast(src); - buffer.pContext = nullptr; - buffer.PlayBegin = 0; - buffer.PlayLength = AUDIO_BUFFER_SAMPLES; - - const u32 res = FAudioSourceVoice_SubmitSourceBuffer(m_source_voice, &buffer, nullptr); - if (res) - { - FAudio_.fatal("FAudioSourceVoice_SubmitSourceBuffer() failed(0x%08x)", res); - 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); + return m_playing; } f32 FAudioBackend::SetFrequencyRatio(f32 new_ratio) { + ensure(m_source_voice != nullptr); + new_ratio = std::clamp(new_ratio, FAUDIO_MIN_FREQ_RATIO, FAUDIO_DEFAULT_FREQ_RATIO); const u32 res = FAudioSourceVoice_SetFrequencyRatio(m_source_voice, new_ratio, FAUDIO_COMMIT_NOW); if (res) { - FAudio_.fatal("FAudioSourceVoice_SetFrequencyRatio() failed(0x%08x)", res); + FAudio_.error("FAudioSourceVoice_SetFrequencyRatio() failed(0x%08x)", res); return 1.0f; } return new_ratio; } + +void FAudioBackend::SetWriteCallback(std::function cb) +{ + std::lock_guard lock(m_cb_mutex); + m_write_callback = cb; +} + +f64 FAudioBackend::GetCallbackFrameLen() +{ + ensure(m_source_voice != nullptr); + + f64 min_latency{}; + const f64 _10ms = 0.01; + + u32 samples_per_q = 0, freq = 0; + FAudio_GetProcessingQuantum(m_instance, &samples_per_q, &freq); + + if (freq) + { + min_latency = static_cast(samples_per_q) / freq; + } + + return std::max(min_latency, _10ms); +} + +void FAudioBackend::OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj, u32 BytesRequired) +{ + FAudioBackend *faudio = static_cast(cb_obj); + + std::unique_lock lock(faudio->m_cb_mutex, std::defer_lock); + if (BytesRequired && lock.try_lock() && faudio->m_write_callback && faudio->m_playing) + { + ensure(BytesRequired <= faudio->m_data_buf_len, "FAudio internal buffer is too small. Report to developers!"); + + const u32 sample_size = faudio->get_sample_size() * faudio->get_channels(); + u32 written = std::min(faudio->m_write_callback(BytesRequired, faudio->m_data_buf.get()), BytesRequired); + written -= written % sample_size; + + if (written >= sample_size) + { + memcpy(faudio->m_last_sample, faudio->m_data_buf.get() + written - sample_size, sample_size); + } + + for (u32 i = written; i < BytesRequired; i += sample_size) + { + memcpy(faudio->m_data_buf.get() + i, faudio->m_last_sample, sample_size); + } + + FAudioBuffer buffer{}; + buffer.AudioBytes = BytesRequired; + buffer.LoopBegin = FAUDIO_NO_LOOP_REGION; + buffer.pAudioData = static_cast(faudio->m_data_buf.get()); + + const u32 res = FAudioSourceVoice_SubmitSourceBuffer(faudio->m_source_voice, &buffer, nullptr); + if (res) + { + FAudio_.error("FAudioSourceVoice_SubmitSourceBuffer() failed(0x%08x)", res); + } + } +} + +void FAudioBackend::OnCriticalError_func(FAudioEngineCallback *cb_obj, u32 Error) +{ + FAudio_.error("OnCriticalError() failed(0x%08x)", Error); + + FAudioBackend *faudio = static_cast(cb_obj); + faudio->m_reset_req = true; +} diff --git a/rpcs3/Emu/Audio/FAudio/FAudioBackend.h b/rpcs3/Emu/Audio/FAudio/FAudioBackend.h index 7d46205c66..0c586cde2c 100644 --- a/rpcs3/Emu/Audio/FAudio/FAudioBackend.h +++ b/rpcs3/Emu/Audio/FAudio/FAudioBackend.h @@ -4,16 +4,14 @@ #error "FAudio support disabled but still being built." #endif +#include +#include "Utilities/mutex.h" #include "Emu/Audio/AudioBackend.h" + #include "FAudio.h" -class FAudioBackend : public AudioBackend +class FAudioBackend : public AudioBackend, public FAudioVoiceCallback, public FAudioEngineCallback { -private: - FAudio* m_instance{}; - FAudioMasteringVoice* m_master_voice{}; - FAudioSourceVoice* m_source_voice{}; - public: FAudioBackend(); ~FAudioBackend() override; @@ -21,27 +19,47 @@ public: FAudioBackend(const FAudioBackend&) = delete; FAudioBackend& operator=(const FAudioBackend&) = delete; - const char* GetName() const override - { - return "FAudio"; - } + const char* GetName() const override { return "FAudio"; } - static const u32 capabilities = PLAY_PAUSE_FLUSH | IS_PLAYING | GET_NUM_ENQUEUED_SAMPLES | SET_FREQUENCY_RATIO; - u32 GetCapabilities() const override - { - return capabilities; - } + static const u32 capabilities = SET_FREQUENCY_RATIO; + u32 GetCapabilities() const override { return capabilities; } - void Open(u32 /* num_buffers */) override; + bool Initialized() override { return m_instance != nullptr; } + bool Operational() override { return m_instance != nullptr && m_source_voice != nullptr && !m_reset_req.observe(); } + + void Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; void Close() override; + void SetWriteCallback(std::function cb) override; + f64 GetCallbackFrameLen() override; + void Play() override; void Pause() override; bool IsPlaying() override; - bool AddData(const void* src, u32 num_samples) override; - void Flush() override; - - u64 GetNumEnqueuedSamples() override; f32 SetFrequencyRatio(f32 new_ratio) override; + +private: + static constexpr u32 INTERNAL_BUF_SIZE_MS = 25; + + FAudio* m_instance{}; + FAudioMasteringVoice* m_master_voice{}; + FAudioSourceVoice* m_source_voice{}; + + shared_mutex m_cb_mutex{}; + std::function m_write_callback{}; + std::unique_ptr m_data_buf{}; + u64 m_data_buf_len = 0; + u8 m_last_sample[sizeof(float) * static_cast(AudioChannelCnt::SURROUND_7_1)]{}; + + bool m_playing = false; + atomic_t m_reset_req = false; + + // FAudio voice callbacks + static void OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj, u32 BytesRequired); + + // FAudio engine callbacks + static void OnCriticalError_func(FAudioEngineCallback *cb_obj, u32 Error); + + void CloseUnlocked(); }; diff --git a/rpcs3/Emu/Audio/Null/NullAudioBackend.h b/rpcs3/Emu/Audio/Null/NullAudioBackend.h index 5e0297b2e6..d894fb1b42 100644 --- a/rpcs3/Emu/Audio/Null/NullAudioBackend.h +++ b/rpcs3/Emu/Audio/Null/NullAudioBackend.h @@ -6,19 +6,23 @@ class NullAudioBackend : public AudioBackend { public: NullAudioBackend() {} - virtual ~NullAudioBackend() {} + ~NullAudioBackend() {} - virtual const char* GetName() const override { return "Null"; } + const char* GetName() const override { return "Null"; } - static const u32 capabilities = PLAY_PAUSE_FLUSH; - virtual u32 GetCapabilities() const override { return capabilities; } + static const u32 capabilities = 0; + u32 GetCapabilities() const override { return capabilities; } - virtual void Open(u32) override {} - virtual void Close() override {} + void Open(AudioFreq /* freq */, AudioSampleSize /* sample_size */, AudioChannelCnt /* ch_cnt */) override { m_playing = false; } + void Close() override { m_playing = false; } - virtual void Play() override {} - virtual void Pause() override {} + void SetWriteCallback(std::function /* cb */) override {}; + f64 GetCallbackFrameLen() override { return 0.01; }; - virtual bool AddData(const void*, u32) override { return true; } - virtual void Flush() override {} + void Play() override { m_playing = true; } + void Pause() override { m_playing = false; } + bool IsPlaying() override { return m_playing; } + +private: + bool m_playing = false; }; diff --git a/rpcs3/Emu/Audio/Pulse/PulseBackend.cpp b/rpcs3/Emu/Audio/Pulse/PulseBackend.cpp deleted file mode 100644 index 8474fafa53..0000000000 --- a/rpcs3/Emu/Audio/Pulse/PulseBackend.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef HAVE_PULSE -#error "PulseAudio support disabled but still being built." -#endif - -#include "Emu/System.h" -#include "PulseBackend.h" - -#include -#include - -PulseBackend::PulseBackend() - : AudioBackend() -{ -} - -PulseBackend::~PulseBackend() -{ - this->Close(); -} - -void PulseBackend::Close() -{ - if (this->connection) - { - pa_simple_free(this->connection); - this->connection = nullptr; - } -} - -void PulseBackend::Open(u32 /* num_buffers */) -{ - pa_sample_spec ss; - ss.format = (m_sample_size == 2) ? PA_SAMPLE_S16LE : PA_SAMPLE_FLOAT32LE; - ss.rate = m_sampling_rate; - - pa_channel_map channel_map; - - if (m_channels == 2) - { - channel_map.channels = 2; - channel_map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; - channel_map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; - } - else if (m_channels == 6) - { - channel_map.channels = 6; - channel_map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; - channel_map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; - channel_map.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; - channel_map.map[3] = PA_CHANNEL_POSITION_LFE; - channel_map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; - channel_map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; - } - else - { - channel_map.channels = 8; - channel_map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; - channel_map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; - channel_map.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; - channel_map.map[3] = PA_CHANNEL_POSITION_LFE; - channel_map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; - channel_map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; - channel_map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; - channel_map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; - } - ss.channels = channel_map.channels; - - int err; - this->connection = pa_simple_new(NULL, "RPCS3", PA_STREAM_PLAYBACK, NULL, "Game", &ss, &channel_map, NULL, &err); - if (!this->connection) - { - fprintf(stderr, "PulseAudio: Failed to initialize audio: %s\n", pa_strerror(err)); - } -} - -bool PulseBackend::AddData(const void* src, u32 num_samples) -{ - AUDIT(this->connection); - - int err; - if (pa_simple_write(this->connection, src, num_samples * m_sample_size, &err) < 0) - { - fprintf(stderr, "PulseAudio: Failed to write audio stream: %s\n", pa_strerror(err)); - return false; - } - - return true; -} diff --git a/rpcs3/Emu/Audio/Pulse/PulseBackend.h b/rpcs3/Emu/Audio/Pulse/PulseBackend.h deleted file mode 100644 index 85f82aa7b5..0000000000 --- a/rpcs3/Emu/Audio/Pulse/PulseBackend.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#ifndef HAVE_PULSE -#error "PulseAudio support disabled but still being built." -#endif - -#include -#include "Emu/Audio/AudioBackend.h" - -class PulseBackend : public AudioBackend -{ -public: - PulseBackend(); - virtual ~PulseBackend() override; - - PulseBackend(const PulseBackend&) = delete; - PulseBackend& operator=(const PulseBackend&) = delete; - - virtual const char* GetName() const override { return "Pulse"; } - - static const u32 capabilities = 0; - virtual u32 GetCapabilities() const override { return capabilities; } - - virtual void Open(u32) override; - virtual void Close() override; - - virtual bool AddData(const void* src, u32 num_samples) override; - -private: - pa_simple* connection{}; -}; diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp index defd8a3cc3..d604ed11ac 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp @@ -22,37 +22,40 @@ XAudio2Backend::XAudio2Backend() // In order to prevent errors on CreateMasteringVoice, apparently we need CoInitializeEx according to: // https://docs.microsoft.com/en-us/windows/win32/api/xaudio2fx/nf-xaudio2fx-xaudio2createvolumemeter HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) + if (SUCCEEDED(hr)) { - XAudio.error("CoInitializeEx() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); - return; + m_com_init_success = true; } - hr = XAudio2Create(instance.GetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR); + hr = XAudio2Create(instance.GetAddressOf(), 0, XAUDIO2_USE_DEFAULT_PROCESSOR); if (FAILED(hr)) { XAudio.error("XAudio2Create() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); return; } - hr = instance->CreateMasteringVoice(&m_master_voice, m_channels, 48000); + hr = instance->CreateMasteringVoice(&m_master_voice); if (FAILED(hr)) { XAudio.error("CreateMasteringVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + instance->StopEngine(); return; } + hr = instance->RegisterForCallbacks(this); + if (FAILED(hr)) + { + // Some error recovery functionality will be lost, but otherwise backend is operational + XAudio.error("RegisterForCallbacks() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + } + // All succeeded, "commit" m_xaudio2_instance = std::move(instance); } XAudio2Backend::~XAudio2Backend() { - if (m_source_voice != nullptr) - { - m_source_voice->Stop(); - m_source_voice->DestroyVoice(); - } + Close(); if (m_master_voice != nullptr) { @@ -62,135 +65,209 @@ XAudio2Backend::~XAudio2Backend() if (m_xaudio2_instance != nullptr) { m_xaudio2_instance->StopEngine(); + m_xaudio2_instance->Release(); } + + if (m_com_init_success) + { + CoUninitialize(); + } +} + +bool XAudio2Backend::Operational() +{ + if (m_dev_listener.output_device_changed()) m_reset_req = true; + + return m_xaudio2_instance != nullptr && m_source_voice != nullptr && !m_reset_req.observe(); } void XAudio2Backend::Play() { - AUDIT(m_source_voice != nullptr); + ensure(m_source_voice != nullptr); + + if (m_playing) return; const HRESULT hr = m_source_voice->Start(); if (FAILED(hr)) { - XAudio.fatal("Start() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + XAudio.error("Start() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + return; } + + std::lock_guard lock(m_cb_mutex); + m_playing = true; } -void XAudio2Backend::Close() +void XAudio2Backend::CloseUnlocked() { - Pause(); - Flush(); -} - -void XAudio2Backend::Pause() -{ - AUDIT(m_source_voice != nullptr); + if (m_source_voice == nullptr) return; const HRESULT hr = m_source_voice->Stop(); if (FAILED(hr)) { - XAudio.fatal("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + XAudio.error("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); } + + m_source_voice->DestroyVoice(); + m_playing = false; + m_source_voice = nullptr; + m_data_buf = nullptr; + m_data_buf_len = 0; + memset(m_last_sample, 0, sizeof(m_last_sample)); } -void XAudio2Backend::Open(u32 /* num_buffers */) +void XAudio2Backend::Close() { - WAVEFORMATEX waveformatex{}; - waveformatex.wFormatTag = m_convert_to_u16 ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT; - waveformatex.nChannels = m_channels; - waveformatex.nSamplesPerSec = m_sampling_rate; - waveformatex.nAvgBytesPerSec = static_cast(m_sampling_rate * m_channels * m_sample_size); - waveformatex.nBlockAlign = m_channels * m_sample_size; - waveformatex.wBitsPerSample = m_sample_size * 8; - waveformatex.cbSize = 0; + std::lock_guard lock(m_cb_mutex); + CloseUnlocked(); +} - const HRESULT hr = m_xaudio2_instance->CreateSourceVoice(&m_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO); +void XAudio2Backend::Pause() +{ + ensure(m_source_voice != nullptr); + + HRESULT hr = m_source_voice->Stop(); if (FAILED(hr)) { - XAudio.fatal("CreateSourceVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + XAudio.error("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + } + + hr = m_source_voice->FlushSourceBuffers(); + if (FAILED(hr)) + { + XAudio.error("FlushSourceBuffers() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + } + + std::lock_guard lock(m_cb_mutex); + m_playing = false; +} + +void XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) +{ + if (m_xaudio2_instance == nullptr) return; + + std::lock_guard lock(m_cb_mutex); + CloseUnlocked(); + + m_sampling_rate = freq; + m_sample_size = sample_size; + m_channels = ch_cnt; + + WAVEFORMATEX waveformatex{}; + waveformatex.wFormatTag = get_convert_to_s16() ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT; + waveformatex.nChannels = get_channels(); + waveformatex.nSamplesPerSec = get_sampling_rate(); + waveformatex.nAvgBytesPerSec = static_cast(get_sampling_rate() * get_channels() * get_sample_size()); + waveformatex.nBlockAlign = get_channels() * get_sample_size(); + waveformatex.wBitsPerSample = get_sample_size() * 8; + waveformatex.cbSize = 0; + + const HRESULT hr = m_xaudio2_instance->CreateSourceVoice(&m_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO, this); + if (FAILED(hr)) + { + XAudio.error("CreateSourceVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); return; } - AUDIT(m_source_voice != nullptr); + ensure(m_source_voice != nullptr); m_source_voice->SetVolume(1.0f); + + m_data_buf_len = get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast(XAUDIO2_DEFAULT_FREQ_RATIO) / 1000; + m_data_buf = std::make_unique(m_data_buf_len); } bool XAudio2Backend::IsPlaying() { - AUDIT(m_source_voice != nullptr); + ensure(m_source_voice != nullptr); - XAUDIO2_VOICE_STATE state; - m_source_voice->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED); - - return state.BuffersQueued > 0 || state.pCurrentBufferContext != nullptr; -} - -bool XAudio2Backend::AddData(const void* src, u32 num_samples) -{ - AUDIT(m_source_voice != nullptr); - - XAUDIO2_VOICE_STATE state; - m_source_voice->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED); - - if (state.BuffersQueued >= MAX_AUDIO_BUFFERS) - { - XAudio.warning("Too many buffers enqueued (%d)", state.BuffersQueued); - return false; - } - - XAUDIO2_BUFFER buffer{}; - buffer.AudioBytes = num_samples * m_sample_size; - buffer.Flags = 0; - buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION; - buffer.LoopCount = 0; - buffer.LoopLength = 0; - buffer.pAudioData = static_cast(src); - buffer.pContext = nullptr; - buffer.PlayBegin = 0; - buffer.PlayLength = AUDIO_BUFFER_SAMPLES; - - const HRESULT hr = m_source_voice->SubmitSourceBuffer(&buffer); - if (FAILED(hr)) - { - XAudio.fatal("AddData() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); - return false; - } - - return true; -} - -void XAudio2Backend::Flush() -{ - AUDIT(m_source_voice != nullptr); - - const HRESULT hr = m_source_voice->FlushSourceBuffers(); - if (FAILED(hr)) - { - XAudio.fatal("FlushSourceBuffers() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); - } -} - -u64 XAudio2Backend::GetNumEnqueuedSamples() -{ - AUDIT(m_source_voice != nullptr); - - XAUDIO2_VOICE_STATE state; - m_source_voice->GetState(&state); - - // all buffers contain AUDIO_BUFFER_SAMPLES, so we can easily calculate how many samples there are remaining - return static_cast(AUDIO_BUFFER_SAMPLES - state.SamplesPlayed % AUDIO_BUFFER_SAMPLES) + (state.BuffersQueued * AUDIO_BUFFER_SAMPLES); + return m_playing; } f32 XAudio2Backend::SetFrequencyRatio(f32 new_ratio) { + ensure(m_source_voice != nullptr); + new_ratio = std::clamp(new_ratio, XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_DEFAULT_FREQ_RATIO); const HRESULT hr = m_source_voice->SetFrequencyRatio(new_ratio); if (FAILED(hr)) { - XAudio.fatal("SetFrequencyRatio() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + XAudio.error("SetFrequencyRatio() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); return 1.0f; } return new_ratio; } + +void XAudio2Backend::SetWriteCallback(std::function cb) +{ + std::lock_guard lock(m_cb_mutex); + m_write_callback = cb; +} + +f64 XAudio2Backend::GetCallbackFrameLen() +{ + ensure(m_source_voice != nullptr); + + void *ext; + f64 min_latency{}; + const f64 _10ms = 0.01; + + HRESULT hr = m_xaudio2_instance->QueryInterface(IID_IXAudio2Extension, &ext); + if (FAILED(hr)) + { + XAudio.error("QueryInterface() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + } + else + { + u32 samples_per_q = 0, freq = 0; + static_cast(ext)->GetProcessingQuantum(&samples_per_q, &freq); + + if (freq) + { + min_latency = static_cast(samples_per_q) / freq; + } + } + + return std::max(min_latency, _10ms); // 10ms is the minimum for XAudio +} + +void XAudio2Backend::OnVoiceProcessingPassStart(UINT32 BytesRequired) +{ + std::unique_lock lock(m_cb_mutex, std::defer_lock); + if (BytesRequired && lock.try_lock() && m_write_callback && m_playing) + { + ensure(BytesRequired <= m_data_buf_len, "XAudio internal buffer is too small. Report to developers!"); + + const u32 sample_size = get_sample_size() * get_channels(); + u32 written = std::min(m_write_callback(BytesRequired, m_data_buf.get()), BytesRequired); + written -= written % sample_size; + + if (written >= sample_size) + { + memcpy(m_last_sample, m_data_buf.get() + written - sample_size, sample_size); + } + + for (u32 i = written; i < BytesRequired; i += sample_size) + { + memcpy(m_data_buf.get() + i, m_last_sample, sample_size); + } + + XAUDIO2_BUFFER buffer{}; + buffer.AudioBytes = BytesRequired; + buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION; + buffer.pAudioData = static_cast(m_data_buf.get()); + + const HRESULT hr = m_source_voice->SubmitSourceBuffer(&buffer); + if (FAILED(hr)) + { + XAudio.error("SubmitSourceBuffer() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + } + } +} + +void XAudio2Backend::OnCriticalError(HRESULT Error) +{ + XAudio.error("OnCriticalError() called: %s (0x%08x)", std::system_category().message(Error), static_cast(Error)); + m_reset_req = true; +} diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h index 4c74d520f8..6245a8e928 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.h @@ -4,19 +4,16 @@ #error "XAudio2 can only be built on Windows." #endif +#include +#include "Utilities/mutex.h" #include "Emu/Audio/AudioBackend.h" +#include "Emu/Audio/audio_device_listener.h" #include #include - -class XAudio2Backend final : public AudioBackend +class XAudio2Backend final : public AudioBackend, public IXAudio2VoiceCallback, public IXAudio2EngineCallback { -private: - Microsoft::WRL::ComPtr m_xaudio2_instance{}; - IXAudio2MasteringVoice* m_master_voice{}; - IXAudio2SourceVoice* m_source_voice{}; - public: XAudio2Backend(); ~XAudio2Backend() override; @@ -26,21 +23,56 @@ public: const char* GetName() const override { return "XAudio2"; } - static const u32 capabilities = PLAY_PAUSE_FLUSH | IS_PLAYING | GET_NUM_ENQUEUED_SAMPLES | SET_FREQUENCY_RATIO; + static const u32 capabilities = SET_FREQUENCY_RATIO; u32 GetCapabilities() const override { return capabilities; } - bool Initialized() const override { return m_xaudio2_instance != nullptr; } + bool Initialized() override { return m_xaudio2_instance != nullptr; } + bool Operational() override; - void Open(u32 /* num_buffers */) override; + void Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override; void Close() override; + void SetWriteCallback(std::function cb) override; + f64 GetCallbackFrameLen() override; + void Play() override; void Pause() override; bool IsPlaying() override; - bool AddData(const void* src, u32 num_samples) override; - void Flush() override; - - u64 GetNumEnqueuedSamples() override; f32 SetFrequencyRatio(f32 new_ratio) override; + +private: + static constexpr u32 INTERNAL_BUF_SIZE_MS = 25; + + Microsoft::WRL::ComPtr m_xaudio2_instance{}; + IXAudio2MasteringVoice* m_master_voice{}; + IXAudio2SourceVoice* m_source_voice{}; + bool m_com_init_success = false; + + shared_mutex m_cb_mutex{}; + std::function m_write_callback{}; + std::unique_ptr m_data_buf{}; + u64 m_data_buf_len = 0; + u8 m_last_sample[sizeof(float) * static_cast(AudioChannelCnt::SURROUND_7_1)]{}; + + bool m_playing = false; + atomic_t m_reset_req = false; + + audio_device_listener m_dev_listener{}; + + // XAudio voice callbacks + void OnVoiceProcessingPassStart(UINT32 BytesRequired) override; + void OnVoiceProcessingPassEnd() override {} + void OnStreamEnd() override {} + void OnBufferStart(void* /* pBufferContext */) override {} + void OnBufferEnd(void* /* pBufferContext*/) override {} + void OnLoopEnd(void* /* pBufferContext */) override {} + void OnVoiceError(void* /* pBufferContext */, HRESULT /* Error */) override {} + + // XAudio engine callbacks + void OnProcessingPassStart() override {}; + void OnProcessingPassEnd() override {}; + void OnCriticalError(HRESULT Error) override; + + void CloseUnlocked(); }; diff --git a/rpcs3/Emu/Audio/audio_device_listener.cpp b/rpcs3/Emu/Audio/audio_device_listener.cpp index 03f86d620c..3bf12e82c0 100644 --- a/rpcs3/Emu/Audio/audio_device_listener.cpp +++ b/rpcs3/Emu/Audio/audio_device_listener.cpp @@ -10,15 +10,26 @@ LOG_CHANNEL(IO); audio_device_listener::audio_device_listener() { #ifdef _WIN32 + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (SUCCEEDED(hr)) + { + m_com_init_success = true; + } + // Try to register a listener for device changes - HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_device_enumerator)); + hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_device_enumerator)); if (hr != S_OK) { IO.error("CoCreateInstance() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); } else if (m_device_enumerator) { - m_device_enumerator->RegisterEndpointNotificationCallback(&m_listener); + hr = m_device_enumerator->RegisterEndpointNotificationCallback(this); + if (FAILED(hr)) + { + IO.error("RegisterEndpointNotificationCallback() failed: %s (0x%08x)", std::system_category().message(hr), static_cast(hr)); + m_device_enumerator->Release(); + } } else { @@ -32,11 +43,27 @@ audio_device_listener::~audio_device_listener() #ifdef _WIN32 if (m_device_enumerator != nullptr) { + m_device_enumerator->UnregisterEndpointNotificationCallback(this); m_device_enumerator->Release(); } + + if (m_com_init_success) + { + CoUninitialize(); + } #endif } +bool audio_device_listener::input_device_changed() +{ + return m_input_device_changed.test_and_reset(); +} + +bool audio_device_listener::output_device_changed() +{ + return m_output_device_changed.test_and_reset(); +} + #ifdef _WIN32 template <> void fmt_class_string::format(std::string& out, u64 arg) @@ -70,7 +97,7 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } -HRESULT audio_device_listener::listener::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) +HRESULT audio_device_listener::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) { IO.notice("OnDefaultDeviceChanged(flow=%s, role=%s, new_default_device_id=0x%x)", flow, role, new_default_device_id); @@ -80,8 +107,9 @@ HRESULT audio_device_listener::listener::OnDefaultDeviceChanged(EDataFlow flow, return S_OK; } - // Only listen for console and communication device changes. - if ((role != eConsole && role != eCommunications) || (flow != eRender && flow != eCapture)) + // Listen only for one device role, otherwise we're going to receive more than one + // notification for flow type + if (role != eConsole) { IO.notice("OnDefaultDeviceChanged(): we don't care about this device"); return S_OK; @@ -90,15 +118,27 @@ HRESULT audio_device_listener::listener::OnDefaultDeviceChanged(EDataFlow flow, const std::wstring tmp(new_default_device_id); const std::string new_device_id = wchar_to_utf8(tmp.c_str()); - if (device_id != new_device_id) + if (flow == eRender || flow == eAll) { - device_id = new_device_id; - - IO.warning("Default device changed: new device = '%s'", device_id); - - if (auto& g_audio = g_fxo->get(); g_fxo->is_init()) + if (output_device_id != new_device_id) { - g_audio.m_update_configuration = true; + output_device_id = new_device_id; + + IO.warning("Default output device changed: new device = '%s'", output_device_id); + + m_output_device_changed = true; + } + } + + if (flow == eCapture || flow == eAll) + { + if (input_device_id != new_device_id) + { + input_device_id = new_device_id; + + IO.warning("Default input device changed: new device = '%s'", input_device_id); + + m_input_device_changed = true; } } diff --git a/rpcs3/Emu/Audio/audio_device_listener.h b/rpcs3/Emu/Audio/audio_device_listener.h index aee9666996..947d7ec6d9 100644 --- a/rpcs3/Emu/Audio/audio_device_listener.h +++ b/rpcs3/Emu/Audio/audio_device_listener.h @@ -4,28 +4,39 @@ #include #endif +#include "util/atomic.hpp" + +#ifdef _WIN32 +class audio_device_listener : public IMMNotificationClient +#else class audio_device_listener +#endif { public: audio_device_listener(); ~audio_device_listener(); + bool input_device_changed(); + bool output_device_changed(); + private: #ifdef _WIN32 - struct listener : public IMMNotificationClient - { - std::string device_id; + std::string input_device_id; + std::string output_device_id; - IFACEMETHODIMP_(ULONG) AddRef() override { return 1; }; - IFACEMETHODIMP_(ULONG) Release() override { return 1; }; - IFACEMETHODIMP QueryInterface(REFIID /*iid*/, void** /*object*/) override { return S_OK; }; - IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR /*device_id*/, const PROPERTYKEY /*key*/) override { return S_OK; }; - IFACEMETHODIMP OnDeviceAdded(LPCWSTR /*device_id*/) override { return S_OK; }; - IFACEMETHODIMP OnDeviceRemoved(LPCWSTR /*device_id*/) override { return S_OK; }; - IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR /*device_id*/, DWORD /*new_state*/) override { return S_OK; }; - IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) override; - } m_listener; + IFACEMETHODIMP_(ULONG) AddRef() override { return 1; }; + IFACEMETHODIMP_(ULONG) Release() override { return 1; }; + IFACEMETHODIMP QueryInterface(REFIID /*iid*/, void** /*object*/) override { return S_OK; }; + IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR /*device_id*/, const PROPERTYKEY /*key*/) override { return S_OK; }; + IFACEMETHODIMP OnDeviceAdded(LPCWSTR /*device_id*/) override { return S_OK; }; + IFACEMETHODIMP OnDeviceRemoved(LPCWSTR /*device_id*/) override { return S_OK; }; + IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR /*device_id*/, DWORD /*new_state*/) override { return S_OK; }; + IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) override; IMMDeviceEnumerator* m_device_enumerator = nullptr; + bool m_com_init_success = false; #endif + + atomic_t m_input_device_changed = false; + atomic_t m_output_device_changed = false; }; diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 4db7d07112..fb17cf441a 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(rpcs3_emu +add_library(rpcs3_emu cache_utils.cpp IdManager.cpp localized_string.cpp @@ -54,6 +54,7 @@ target_sources(rpcs3_emu PRIVATE ../../Utilities/mutex.cpp ../../Utilities/rXml.cpp ../../Utilities/sema.cpp + ../../Utilities/simple_ringbuf.cpp ../../Utilities/StrFmt.cpp ../../Utilities/Thread.cpp ../../Utilities/version.cpp @@ -110,22 +111,9 @@ target_sources(rpcs3_emu PRIVATE Audio/audio_device_listener.cpp Audio/AudioDumper.cpp Audio/AudioBackend.cpp - Audio/AL/OpenALBackend.cpp + Audio/Cubeb/CubebBackend.cpp ) -if(USE_ALSA) - find_package(ALSA) - if(ALSA_FOUND) - target_sources(rpcs3_emu PRIVATE Audio/ALSA/ALSABackend.cpp) - target_link_libraries(rpcs3_emu PUBLIC 3rdparty::alsa) - endif() -endif() - -if(USE_PULSE AND PULSE_FOUND) - target_sources(rpcs3_emu PRIVATE Audio/Pulse/PulseBackend.cpp) - target_link_libraries(rpcs3_emu PUBLIC 3rdparty::pulse) -endif() - if(USE_FAUDIO) find_package(SDL2) if(SDL2_FOUND AND NOT SDL2_VERSION VERSION_LESS 2.0.9) @@ -147,6 +135,9 @@ target_link_libraries(rpcs3_emu PUBLIC 3rdparty::openal) +target_link_libraries(rpcs3_emu + PUBLIC + 3rdparty::cubeb) # Cell target_sources(rpcs3_emu PRIVATE diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.cpp b/rpcs3/Emu/Cell/Modules/cellAudio.cpp index f39698a25d..8e1576a6e1 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAudio.cpp @@ -47,24 +47,52 @@ void fmt_class_string::format(std::string& out, u64 arg) cell_audio_config::cell_audio_config() { raw = audio::get_raw_config(); - reset(); } -void cell_audio_config::reset() +void cell_audio_config::reset(bool backend_changed) { - backend.reset(); - backend = Emu.GetCallbacks().get_audio(); + if (!backend || backend_changed) + { + backend.reset(); + backend = Emu.GetCallbacks().get_audio(); + } + + { + std::string str; + backend->dump_capabilities(str); + cellAudio.notice("cellAudio initializing. Backend: %s, Capabilities: %s", backend->GetName(), str.c_str()); + } + + const AudioFreq freq = AudioFreq::FREQ_48K; + const AudioSampleSize sample_size = g_cfg.audio.convert_to_s16 ? AudioSampleSize::S16 : AudioSampleSize::FLOAT; + const AudioChannelCnt ch_cnt = [&]() + { + const audio_downmix downmix = g_cfg.audio.audio_channel_downmix.get(); + + switch (downmix) + { + case audio_downmix::no_downmix: return AudioChannelCnt::SURROUND_7_1; + case audio_downmix::downmix_to_5_1: return AudioChannelCnt::SURROUND_5_1; + case audio_downmix::downmix_to_stereo: return AudioChannelCnt::STEREO; + case audio_downmix::use_application_settings: return AudioChannelCnt::STEREO; // TODO + default: + fmt::throw_exception("Unknown audio channel mode %s (%d)", downmix, static_cast(downmix)); + } + }(); + + backend->Open(freq, sample_size, ch_cnt); audio_channels = backend->get_channels(); audio_sampling_rate = backend->get_sampling_rate(); audio_block_period = AUDIO_BUFFER_SAMPLES * 1000000 / audio_sampling_rate; + audio_sample_size = backend->get_sample_size(); + audio_min_buffer_duration = backend->GetCallbackFrameLen(); audio_buffer_length = AUDIO_BUFFER_SAMPLES * audio_channels; - audio_buffer_size = audio_buffer_length * backend->get_sample_size(); + audio_buffer_size = audio_buffer_length * audio_sample_size; desired_buffer_duration = raw.desired_buffer_duration * 1000llu; - - buffering_enabled = raw.buffering_enabled && backend->has_capability(AudioBackend::PLAY_PAUSE_FLUSH | AudioBackend::IS_PLAYING); + buffering_enabled = raw.buffering_enabled; minimum_block_period = audio_block_period / 2; maximum_block_period = (6 * audio_block_period) / 5; @@ -92,7 +120,6 @@ void cell_audio_config::reset() } } - audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg) : backend(_cfg.backend) , cfg(_cfg) @@ -113,32 +140,29 @@ audio_ringbuffer::audio_ringbuffer(cell_audio_config& _cfg) // Init audio dumper if enabled if (g_cfg.audio.dump_to_file) { - m_dump.reset(new AudioDumper(cfg.audio_channels)); + m_dump.reset(new AudioDumper(cfg.audio_channels, cfg.audio_sampling_rate, cfg.audio_sample_size)); } - // Initialize backend + const f64 buffer_dur_mult = [&]() { - std::string str; - backend->dump_capabilities(str); - cellAudio.notice("cellAudio initializing. Backend: %s, Capabilities: %s", backend->GetName(), str.c_str()); - } + const f64 min_buf_dur = _cfg.audio_min_buffer_duration + 0.01; // Add 10ms to allow jitter compensation + if (cfg.raw.buffering_enabled) + { + return std::max(min_buf_dur, cfg.raw.desired_buffer_duration / 1000.0 * 2); // Allocate 2x buffer to keep buffering algorithm happy + } - backend->Open(cfg.num_allocated_buffers); - backend_open = true; + return min_buf_dur; + }(); - ensure(!get_backend_playing()); + cb_ringbuf.set_buf_size(static_cast(_cfg.audio_channels * _cfg.audio_sampling_rate * _cfg.audio_sample_size * buffer_dur_mult)); + backend->SetWriteCallback(std::bind(&audio_ringbuffer::backend_write_callback, this, std::placeholders::_1, std::placeholders::_2)); } audio_ringbuffer::~audio_ringbuffer() { - if (!backend_open) + if (get_backend_playing()) { - return; - } - - if (get_backend_playing() && has_capability(AudioBackend::PLAY_PAUSE_FLUSH)) - { - backend->Pause(); + flush(); } backend->Close(); @@ -159,6 +183,13 @@ f32 audio_ringbuffer::set_frequency_ratio(f32 new_ratio) return frequency_ratio; } +u32 audio_ringbuffer::backend_write_callback(u32 size, void *buf) +{ + if (!backend_active.observe()) backend_active = true; + + return cb_ringbuf.pop(buf, size); +} + u64 audio_ringbuffer::get_timestamp() { return get_system_time(); @@ -183,28 +214,28 @@ void audio_ringbuffer::enqueue(const float* in_buffer) m_dump->WriteData(buf, cfg.audio_buffer_size); } - // Enqueue audio - const bool success = backend->AddData(buf, AUDIO_BUFFER_SAMPLES * cfg.audio_channels); - if (!success) + enqueued_samples += AUDIO_BUFFER_SAMPLES; + + // Start playing audio + play(); + + if (!backend_active.observe()) { - cellAudio.warning("Could not enqueue buffer onto audio backend. Attempting to recover..."); - flush(); + // backend is not ready yet return; } - if (has_capability(AudioBackend::PLAY_PAUSE_FLUSH)) - { - enqueued_samples += AUDIO_BUFFER_SAMPLES; + // Enqueue audio + const u32 data_size = AUDIO_BUFFER_SAMPLES * cfg.audio_sample_size * cfg.audio_channels; - // Start playing audio - play(); + if (cb_ringbuf.get_free_size() >= data_size) + { + cb_ringbuf.push(buf, data_size); } } void audio_ringbuffer::enqueue_silence(u32 buf_count) { - AUDIT(has_capability(AudioBackend::PLAY_PAUSE_FLUSH)); - for (u32 i = 0; i < buf_count; i++) { enqueue(silence_buffer); @@ -213,13 +244,7 @@ void audio_ringbuffer::enqueue_silence(u32 buf_count) void audio_ringbuffer::play() { - if (!has_capability(AudioBackend::PLAY_PAUSE_FLUSH)) - { - playing = true; - return; - } - - if (playing && has_capability(AudioBackend::IS_PLAYING)) + if (playing) { return; } @@ -239,19 +264,12 @@ void audio_ringbuffer::play() void audio_ringbuffer::flush() { - if (!has_capability(AudioBackend::PLAY_PAUSE_FLUSH)) - { - playing = false; - return; - } - //cellAudio.trace("Flushing an estimated %llu enqueued samples", enqueued_samples); backend->Pause(); + cb_ringbuf.flush(); playing = false; - backend->Flush(); - if (frequency_ratio != 1.0f) { set_frequency_ratio(1.0f); @@ -266,16 +284,16 @@ u64 audio_ringbuffer::update() if (Emu.IsPaused()) { // Emulator paused - if (playing && has_capability(AudioBackend::PLAY_PAUSE_FLUSH)) + if (playing) { - backend->Pause(); + flush(); } emu_paused = true; } else if (emu_paused) { // Emulator unpaused - if (enqueued_samples > 0 && has_capability(AudioBackend::PLAY_PAUSE_FLUSH)) + if (enqueued_samples > 0) { play(); } @@ -289,37 +307,7 @@ u64 audio_ringbuffer::update() // Calculate how many audio samples have played since last time if (cfg.buffering_enabled && (playing || new_playing)) { - if (has_capability(AudioBackend::GET_NUM_ENQUEUED_SAMPLES)) - { - // Backend supports querying for the remaining playtime, so just ask it - enqueued_samples = backend->GetNumEnqueuedSamples(); - } - else - { - const u64 play_delta = (update_timestamp ? timestamp - std::max(play_timestamp, update_timestamp) : 0); - - const u64 delta_samples_tmp = play_delta * static_cast(cfg.audio_sampling_rate * frequency_ratio) + last_remainder; - last_remainder = delta_samples_tmp % 1'000'000; - const u64 delta_samples = delta_samples_tmp / 1'000'000; - - //cellAudio.error("play_delta=%llu delta_samples=%llu", play_delta, delta_samples); - if (delta_samples > 0) - { - if (enqueued_samples < delta_samples) - { - enqueued_samples = 0; - } - else - { - enqueued_samples -= delta_samples; - } - - if (enqueued_samples == 0) - { - cellAudio.trace("Audio buffer about to underrun!"); - } - } - } + enqueued_samples = cb_ringbuf.get_used_size() / cfg.audio_sample_size; } // Update playing state @@ -549,16 +537,20 @@ namespace audio .desired_buffer_duration = g_cfg.audio.desired_buffer_duration, .enable_time_stretching = static_cast(g_cfg.audio.enable_time_stretching), .time_stretching_threshold = g_cfg.audio.time_stretching_threshold, - .convert_to_u16 = static_cast(g_cfg.audio.convert_to_u16), - .start_threshold = static_cast(g_cfg.audio.start_threshold), - .sampling_period_multiplier = static_cast(g_cfg.audio.sampling_period_multiplier), + .convert_to_s16 = static_cast(g_cfg.audio.convert_to_s16), .downmix = g_cfg.audio.audio_channel_downmix, - .renderer = g_cfg.audio.renderer + .renderer = g_cfg.audio.renderer, + .provider = g_cfg.audio.provider }; } void configure_audio() { + if (g_cfg.audio.provider != audio_provider::cell_audio) + { + return; + } + if (auto& g_audio = g_fxo->get(); g_fxo->is_init()) { // Only reboot the audio renderer if a relevant setting changed @@ -569,20 +561,18 @@ namespace audio raw.buffering_enabled != new_raw.buffering_enabled || raw.time_stretching_threshold != new_raw.time_stretching_threshold || raw.enable_time_stretching != new_raw.enable_time_stretching || - raw.convert_to_u16 != new_raw.convert_to_u16 || - raw.start_threshold != new_raw.start_threshold || - raw.sampling_period_multiplier != new_raw.sampling_period_multiplier || + raw.convert_to_s16 != new_raw.convert_to_s16 || raw.downmix != new_raw.downmix || raw.renderer != new_raw.renderer) { g_audio.cfg.raw = new_raw; - g_audio.m_update_configuration = true; + g_audio.m_update_configuration = raw.renderer != new_raw.renderer ? audio_backend_update::ALL : audio_backend_update::PARAM; } } } } -void cell_audio_thread::update_config() +void cell_audio_thread::update_config(bool backend_changed) { std::lock_guard lock(mutex); @@ -590,36 +580,72 @@ void cell_audio_thread::update_config() ringbuffer.reset(); // Reload config - cfg.reset(); + cfg.reset(backend_changed); // Allocate ringbuffer ringbuffer.reset(new audio_ringbuffer(cfg)); + + // Reset thread state + reset_counters(); +} + +void cell_audio_thread::reset_counters() +{ + m_counter = 0; + m_start_time = ringbuffer->get_timestamp(); + m_last_period_end = m_start_time; + m_dynamic_period = 0; + m_backend_failed = false; } void cell_audio_thread::operator()() { + if (cfg.raw.provider != audio_provider::cell_audio) + { + return; + } + thread_ctrl::scoped_priority high_prio(+1); + // Init audio config + cfg.reset(); + // Allocate ringbuffer ringbuffer.reset(new audio_ringbuffer(cfg)); // Initialize loop variables - m_counter = 0; - m_start_time = ringbuffer->get_timestamp(); - m_last_period_end = m_start_time; - m_dynamic_period = 0; + reset_counters(); u32 untouched_expected = 0; - //u32 in_progress_expected = 0; // Main cellAudio loop while (thread_ctrl::state() != thread_state::aborting) { - if (m_update_configuration) + const auto update_req = m_update_configuration.observe(); + if (update_req != audio_backend_update::NONE) { cellAudio.warning("Updating cell_audio_thread configuration"); - update_config(); - m_update_configuration = false; + update_config(update_req == audio_backend_update::ALL); + m_update_configuration = audio_backend_update::NONE; + } + + if (!ringbuffer->get_operational_status()) + { + cellAudio.warning("Backend stopped unexpectedly (likely device change). Attempting to recover..."); + + if (m_backend_failed) + { + thread_ctrl::wait_for(500 * 1000); + } + + update_config(true); + m_backend_failed = true; + continue; + } + else if (m_backend_failed) + { + cellAudio.warning("Backend recovered"); + m_backend_failed = false; } const u64 timestamp = ringbuffer->update(); @@ -1063,9 +1089,9 @@ void cell_audio_thread::mix(float *out_buffer, s32 offset) { std::memset(out_buffer, 0, out_buffer_sz * sizeof(float)); } - else if (cfg.backend->get_convert_to_u16()) + else if (cfg.backend->get_convert_to_s16()) { - // convert the data from float to u16 with clipping: + // convert the data from float to s16 with clipping: // 2x MULPS // 2x MAXPS (optional) // 2x MINPS (optional) diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.h b/rpcs3/Emu/Cell/Modules/cellAudio.h index c3636939d2..e41d430a99 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.h +++ b/rpcs3/Emu/Cell/Modules/cellAudio.h @@ -2,8 +2,8 @@ #include "Emu/Memory/vm_ptr.h" #include "Utilities/Thread.h" +#include "Utilities/simple_ringbuf.h" #include "Emu/Memory/vm.h" -#include "Emu/Audio/audio_device_listener.h" #include "Emu/Audio/AudioBackend.h" #include "Emu/Audio/AudioDumper.h" #include "Emu/system_config_types.h" @@ -73,6 +73,13 @@ enum CELL_AUDIO_STATUS_RUN = 2, }; +enum class audio_backend_update : u32 +{ + NONE, + PARAM, + ALL, +}; + //libaudio datatypes struct CellAudioPortParam { @@ -196,11 +203,10 @@ struct cell_audio_config s64 desired_buffer_duration = 0; bool enable_time_stretching = false; s64 time_stretching_threshold = 0; - bool convert_to_u16 = false; - u32 start_threshold = 0; - u32 sampling_period_multiplier = 0; + bool convert_to_s16 = false; audio_downmix downmix = audio_downmix::downmix_to_stereo; audio_renderer renderer = audio_renderer::null; + audio_provider provider = audio_provider::none; } raw; std::shared_ptr backend = nullptr; @@ -208,6 +214,8 @@ struct cell_audio_config u32 audio_channels = 0; u32 audio_sampling_rate = 0; u32 audio_block_period = 0; + u32 audio_sample_size = 0; + f64 audio_min_buffer_duration = 0.0; u32 audio_buffer_length = 0; u32 audio_buffer_size = 0; @@ -254,7 +262,7 @@ struct cell_audio_config /* * Config changes */ - void reset(); + void reset(bool backend_changed = true); }; class audio_ringbuffer @@ -271,7 +279,9 @@ private: std::unique_ptr buffer[MAX_AUDIO_BUFFERS]; const float silence_buffer[u32{AUDIO_MAX_CHANNELS_COUNT} * u32{AUDIO_BUFFER_SAMPLES}] = { 0 }; - bool backend_open = false; + simple_ringbuf cb_ringbuf{}; + + atomic_t backend_active = false; bool playing = false; bool emu_paused = false; @@ -287,9 +297,11 @@ private: bool get_backend_playing() const { - return has_capability(AudioBackend::PLAY_PAUSE_FLUSH | AudioBackend::IS_PLAYING) ? backend->IsPlaying() : playing; + return backend->IsPlaying(); } + u32 backend_write_callback(u32 size, void *buf); + public: audio_ringbuffer(cell_audio_config &cfg); ~audio_ringbuffer(); @@ -343,6 +355,11 @@ public: return backend->has_capability(cap); } + bool get_operational_status() const + { + return backend->Operational(); + } + const char* get_backend_name() const { return backend->GetName(); @@ -354,7 +371,6 @@ class cell_audio_thread { private: std::unique_ptr ringbuffer; - audio_device_listener listener; void reset_ports(s32 offset = 0); void advance(u64 timestamp, bool reset = true); @@ -368,11 +384,12 @@ private: return (time_left > 350) ? time_left - 250 : 100; } - void update_config(); + void update_config(bool backend_changed); + void reset_counters(); public: cell_audio_config cfg; - atomic_t m_update_configuration = false; + atomic_t m_update_configuration = audio_backend_update::NONE; shared_mutex mutex; atomic_t init = 0; @@ -396,6 +413,7 @@ public: u64 m_start_time = 0; u64 m_dynamic_period = 0; f32 m_average_playtime = 0.0f; + bool m_backend_failed = false; void operator()(); diff --git a/rpcs3/Emu/Cell/lv2/sys_uart.h b/rpcs3/Emu/Cell/lv2/sys_uart.h index 2408c68f35..94519cff26 100644 --- a/rpcs3/Emu/Cell/lv2/sys_uart.h +++ b/rpcs3/Emu/Cell/lv2/sys_uart.h @@ -3,6 +3,7 @@ #include "Emu/Memory/vm_ptr.h" #include "Utilities/mutex.h" #include "Utilities/cond.h" +#include "Utilities/simple_ringbuf.h" enum : u32 { @@ -108,85 +109,6 @@ struct ps3av_cmd virtual bool execute(vuart_av_thread &vuart, const void *pkt_buf, u32 reply_max_size) = 0; }; -template -class vuart_av_ringbuf -{ -private: - - atomic_t rd_ptr = 0; - atomic_t wr_ptr = 0; - u8 buf[Size + 1]; - -public: - - u32 get_free_size() - { - const u32 rd = rd_ptr.observe(); - const u32 wr = wr_ptr.observe(); - - return wr >= rd ? Size - (wr - rd) : rd - wr - 1U; - } - - u32 get_used_size() - { - return Size - get_free_size(); - } - - u32 push(const void *data, u32 size) - { - const u32 old = wr_ptr.observe(); - const u32 to_push = std::min(size, get_free_size()); - const u8 *b_data = static_cast(data); - - if (!to_push || !data) - { - return 0; - } - - if (old + to_push > sizeof(buf)) - { - const u32 first_write_sz = sizeof(buf) - old; - memcpy(&buf[old], b_data, first_write_sz); - memcpy(&buf[0], b_data + first_write_sz, to_push - first_write_sz); - } - else - { - memcpy(&buf[old], b_data, to_push); - } - - wr_ptr = (old + to_push) % sizeof(buf); - - return to_push; - } - - u32 pop(void *data, u32 size) - { - const u32 old = rd_ptr.observe(); - const u32 to_pop = std::min(size, get_used_size()); - u8 *b_data = static_cast(data); - - if (!to_pop || !data) - { - return 0; - } - - if (old + to_pop > sizeof(buf)) - { - const u32 first_read_sz = sizeof(buf) - old; - memcpy(b_data, &buf[old], first_read_sz); - memcpy(b_data + first_read_sz, &buf[0], to_pop - first_read_sz); - } - else - { - memcpy(b_data, &buf[old], to_pop); - } - - rd_ptr = (old + to_pop) % sizeof(buf); - - return to_pop; - } -}; - class vuart_av_thread { public: @@ -216,10 +138,10 @@ public: private: - vuart_av_ringbuf tx_buf{}; - vuart_av_ringbuf rx_buf{}; + simple_ringbuf tx_buf{PS3AV_TX_BUF_SIZE}; + simple_ringbuf rx_buf{PS3AV_RX_BUF_SIZE}; u8 temp_tx_buf[PS3AV_TX_BUF_SIZE]; - u8 temp_rx_buf[PS3AV_TX_BUF_SIZE]; + u8 temp_rx_buf[PS3AV_RX_BUF_SIZE]; u32 temp_rx_buf_size = 0; u32 read_tx_data(void *data, u32 data_sz); diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index dd563b80b6..bb8c5dd8b9 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -213,23 +213,14 @@ struct cfg_root : cfg::node { node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {} - cfg::_enum renderer{ this, "Renderer", -#ifdef _WIN32 - audio_renderer::xaudio, true }; -#elif HAVE_FAUDIO - audio_renderer::faudio, true }; -#else - audio_renderer::openal, true }; -#endif - + cfg::_enum renderer{ this, "Renderer", audio_renderer::cubeb, true }; + cfg::_enum provider{ this, "Audio provider", audio_provider::cell_audio, false }; cfg::_bool dump_to_file{ this, "Dump to file" }; - cfg::_bool convert_to_u16{ this, "Convert to 16 bit", false, true }; + cfg::_bool convert_to_s16{ this, "Convert to 16 bit", false, true }; cfg::_enum audio_channel_downmix{ this, "Audio Channels", audio_downmix::downmix_to_stereo, true }; - cfg::_int<1, 128> start_threshold{ this, "Start Threshold", 1, true }; // TODO: used only by ALSA, should probably be removed once ALSA is upgraded cfg::_int<0, 200> volume{ this, "Master Volume", 100, true }; cfg::_bool enable_buffering{ this, "Enable Buffering", true, true }; cfg::_int <4, 250> desired_buffer_duration{ this, "Desired Audio Buffer Duration", 100, true }; - cfg::_int<1, 1000> sampling_period_multiplier{ this, "Sampling Period Multiplier", 100, true }; cfg::_bool enable_time_stretching{ this, "Enable Time Stretching", false, true }; cfg::_int<0, 100> time_stretching_threshold{ this, "Time Stretching Threshold", 75, true }; cfg::_enum microphone_type{ this, "Microphone Type", microphone_handler::null }; diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index 3c6b744934..f2a1a36edb 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -109,13 +109,7 @@ void fmt_class_string::format(std::string& out, u64 arg) #ifdef _WIN32 case audio_renderer::xaudio: return "XAudio2"; #endif -#ifdef HAVE_ALSA - case audio_renderer::alsa: return "ALSA"; -#endif -#ifdef HAVE_PULSE - case audio_renderer::pulse: return "PulseAudio"; -#endif - case audio_renderer::openal: return "OpenAL"; + case audio_renderer::cubeb: return "Cubeb"; #ifdef HAVE_FAUDIO case audio_renderer::faudio: return "FAudio"; #endif @@ -472,6 +466,22 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](audio_provider value) + { + switch (value) + { + case audio_provider::none: return "None"; + case audio_provider::cell_audio: return "CellAudio"; + case audio_provider::rsxaudio: return "RSXAudio"; + } + + return unknown; + }); +} + template <> void fmt_class_string::format(std::string& out, u64 arg) { diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index c68ba33d2c..59d26fbfea 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -54,18 +54,19 @@ enum class audio_renderer #ifdef _WIN32 xaudio, #endif -#ifdef HAVE_ALSA - alsa, -#endif - openal, -#ifdef HAVE_PULSE - pulse, -#endif + cubeb, #ifdef HAVE_FAUDIO faudio, #endif }; +enum class audio_provider +{ + none, + cell_audio, + rsxaudio +}; + enum class audio_downmix { no_downmix, // Surround 7.1 diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index eda616cd9e..13d8788a15 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -52,16 +52,10 @@ - - true - true - - true - @@ -142,6 +136,9 @@ NotUsing + + NotUsing + NotUsing @@ -436,16 +433,10 @@ - - true - true - - true - @@ -797,4 +788,4 @@ - \ No newline at end of file + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index a682048b9b..5106eac89a 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -70,12 +70,6 @@ {7555ff6f-67a9-4d02-b744-0bf896751edb} - - {c76057f1-bb19-4981-8ab0-5ba20ac27552} - - - {84c0e40e-efb6-4cfd-b2f5-13c720b09105} - {d055ca32-157a-4d8c-895e-29509858fcb0} @@ -141,6 +135,9 @@ Loader + + Utilities + Utilities @@ -924,12 +921,6 @@ Emu\Audio\FAudio - - Emu\Audio\ALSA - - - Emu\Audio\Pulse - Utilities @@ -1884,12 +1875,6 @@ Emu\Audio\FAudio - - Emu\Audio\ALSA - - - Emu\Audio\Pulse - Utilities @@ -2038,4 +2023,4 @@ Emu\GPU\RSX\Common\Interpreter - \ No newline at end of file + diff --git a/rpcs3/main_application.cpp b/rpcs3/main_application.cpp index 02f533201a..7e02bb1714 100644 --- a/rpcs3/main_application.cpp +++ b/rpcs3/main_application.cpp @@ -18,16 +18,10 @@ #include "Emu/Audio/AudioBackend.h" #include "Emu/Audio/Null/NullAudioBackend.h" -#include "Emu/Audio/AL/OpenALBackend.h" +#include "Emu/Audio/Cubeb/CubebBackend.h" #ifdef _WIN32 #include "Emu/Audio/XAudio2/XAudio2Backend.h" #endif -#ifdef HAVE_ALSA -#include "Emu/Audio/ALSA/ALSABackend.h" -#endif -#ifdef HAVE_PULSE -#include "Emu/Audio/Pulse/PulseBackend.h" -#endif #ifdef HAVE_FAUDIO #include "Emu/Audio/FAudio/FAudioBackend.h" #endif @@ -114,14 +108,7 @@ EmuCallbacks main_application::CreateCallbacks() #ifdef _WIN32 case audio_renderer::xaudio: result = std::make_shared(); break; #endif -#ifdef HAVE_ALSA - case audio_renderer::alsa: result = std::make_shared(); break; -#endif -#ifdef HAVE_PULSE - case audio_renderer::pulse: result = std::make_shared(); break; -#endif - - case audio_renderer::openal: result = std::make_shared(); break; + case audio_renderer::cubeb: result = std::make_shared(); break; #ifdef HAVE_FAUDIO case audio_renderer::faudio: result = std::make_shared(); break; #endif diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index dbbd6de098..56a9313457 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -71,7 +71,7 @@ - ..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\release;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) + ..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\release;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) -Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) release\ false @@ -88,7 +88,7 @@ Level3 - Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimedia.lib;Qt5MultimediaWidgets.lib;Qt5Svg.lib;%(AdditionalDependencies) + Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimedia.lib;Qt5MultimediaWidgets.lib;Qt5Svg.lib;libcubeb.lib;cubeb.lib;Avrt.lib;%(AdditionalDependencies) ..\3rdparty\OpenAL\libs\Win64;..\3rdparty\glslang\build\hlsl\Release;..\3rdparty\glslang\build\SPIRV\Release;..\3rdparty\glslang\build\OGLCompilersDLL\Release;..\3rdparty\glslang\build\glslang\OSDependent\Windows\Release;..\3rdparty\glslang\build\glslang\Release;..\3rdparty\SPIRV\build\source\Release;..\3rdparty\SPIRV\build\source\opt\Release;..\lib\$(CONFIGURATION)-$(PLATFORM);..\3rdparty\XAudio2Redist\libs;..\3rdparty\discord-rpc\lib;$(QTDIR)\lib;%(AdditionalLibraryDirectories);$(VULKAN_SDK)\Lib "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions) true @@ -122,7 +122,7 @@ - ..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\debug;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) + ..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\debug;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) -Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) debug\ false @@ -139,7 +139,7 @@ $(IntDir)vc$(PlatformToolsetVersion).pdb - Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmaind.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgetsd.lib;$(QTDIR)\lib\Qt5Guid.lib;$(QTDIR)\lib\Qt5Cored.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5WinExtrasd.lib;Qt5Concurrentd.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimediad.lib;Qt5MultimediaWidgetsd.lib;Qt5Svgd.lib;%(AdditionalDependencies) + Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmaind.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgetsd.lib;$(QTDIR)\lib\Qt5Guid.lib;$(QTDIR)\lib\Qt5Cored.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5WinExtrasd.lib;Qt5Concurrentd.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimediad.lib;Qt5MultimediaWidgetsd.lib;Qt5Svgd.lib;libcubeb.lib;cubeb.lib;Avrt.lib;%(AdditionalDependencies) ..\3rdparty\OpenAL\libs\Win64;..\3rdparty\glslang\build\hlsl\Debug;..\3rdparty\glslang\build\SPIRV\Debug;..\3rdparty\glslang\build\OGLCompilersDLL\Debug;..\3rdparty\glslang\build\glslang\OSDependent\Windows\Debug;..\3rdparty\glslang\build\glslang\Debug;..\3rdparty\SPIRV\build\source\opt\Debug;..\3rdparty\XAudio2Redist\libs;..\3rdparty\discord-rpc\lib;..\lib\$(CONFIGURATION)-$(PLATFORM);$(QTDIR)\lib;%(AdditionalLibraryDirectories);$(VULKAN_SDK)\Lib "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /VERBOSE %(AdditionalOptions) true diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 36dc172d72..9fd65f55d3 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -965,13 +965,7 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ #ifdef _WIN32 case audio_renderer::xaudio: return tr("XAudio2", "Audio renderer"); #endif -#ifdef HAVE_ALSA - case audio_renderer::alsa: return tr("ALSA", "Audio renderer"); -#endif -#ifdef HAVE_PULSE - case audio_renderer::pulse: return tr("PulseAudio", "Audio renderer"); -#endif - case audio_renderer::openal: return tr("OpenAL", "Audio renderer"); + case audio_renderer::cubeb: return tr("Cubeb", "Audio renderer"); #ifdef HAVE_FAUDIO case audio_renderer::faudio: return tr("FAudio", "Audio renderer"); #endif diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 8892c57776..e3f12d63eb 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -479,7 +479,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceComboBox(ui->shaderCompilerThreads, emu_settings_type::ShaderCompilerNumThreads, true); SubscribeTooltip(ui->gb_shader_compiler_threads, tooltips.settings.shader_compiler_threads); ui->shaderCompilerThreads->setItemText(ui->shaderCompilerThreads->findData(0), tr("Auto", "Number of Shader Compiler Threads")); - + // Custom control that simplifies operation of two independent variables. Can probably be done better but this works. ui->zcullPrecisionMode->addItem(tr("Precise (Default)"), static_cast(zcull_precision_level::precise)); ui->zcullPrecisionMode->addItem(tr("Approximate (Fast)"), static_cast(zcull_precision_level::approximate)); @@ -822,7 +822,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std const QVariantList var_list = ui->audioOutBox->itemData(index).toList(); ensure(var_list.size() == 2 && var_list[0].canConvert()); const QString text = var_list[0].toString(); - const bool enabled = text == "XAudio2" || text == "OpenAL" || text == "FAudio"; + const bool enabled = text == "Cubeb" || text == "XAudio2" || text == "FAudio"; ui->enableBuffering->setEnabled(enabled); enable_buffering_options(enabled && ui->enableBuffering->isChecked()); }; diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index a25a9b72a7..19bf06e0d4 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -41,13 +41,13 @@ public: // audio - const QString audio_out = tr("XAudio2 is the recommended option and should be used whenever possible.\nOpenAL uses a cross-platform approach and is the next best alternative."); - const QString audio_out_linux = tr("OpenAL uses a cross-platform approach and supports audio buffering, so it is the recommended option.\nPulseAudio uses the native Linux sound system, and is the next best alternative. If neither are available, ALSA can be used instead."); + const QString audio_out = tr("Cubeb uses a cross-platform approach and supports audio buffering, so it is the recommended option.\nXAudio2 uses native Windows sounds system, is the next best alternative."); + const QString audio_out_linux = tr("Cubeb uses a cross-platform approach and supports audio buffering, so it is the recommended option.\nIf it's not availiable, FAudio could be used instead."); const QString audio_dump = tr("Saves all audio as a raw wave file. If unsure, leave this unchecked."); const QString convert = tr("Uses 16-bit audio samples instead of default 32-bit floating point.\nUse with buggy audio drivers if you have no sound or completely broken sound."); const QString downmix = tr("Uses chosen audio output instead of default 7.1 surround sound.\nUse downmix to stereo with stereo audio devices. Use 5.1 or higher only if you are using a surround sound audio system."); const QString master_volume = tr("Controls the overall volume of the emulation.\nValues above 100% might reduce the audio quality."); - const QString enable_buffering = tr("Enables audio buffering, which reduces crackle/stutter but increases audio latency (requires XAudio2 or OpenAL)."); + const QString enable_buffering = tr("Enables audio buffering, which reduces crackle/stutter but increases audio latency."); const QString audio_buffer_duration = tr("Target buffer duration in milliseconds.\nHigher values make the buffering algorithm's job easier, but may introduce noticeable audio latency."); const QString enable_time_stretching = tr("Enables time stretching - requires buffering to be enabled.\nReduces crackle/stutter further, but may cause a very noticeable reduction in audio quality on slower CPUs."); const QString time_stretching_threshold = tr("Buffer fill level (in percentage) below which time stretching will start.");