1
0
mirror of https://github.com/RPCS3/rpcs3 synced 2024-07-08 19:56:08 +00:00

Audio backend improvements

Callback based audio update.
Upgraded common backend interface.
Added Cubeb backend.
Support multiple audio providers.
Dropped pulse, alsa, openal backends.
This commit is contained in:
Vestrel 2021-11-25 03:41:05 +09:00 committed by GitHub
parent a84223bdc6
commit 37a722cc1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1458 additions and 1329 deletions

4
.gitmodules vendored
View File

@ -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

View File

@ -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})

12
3rdparty/cubeb/CMakeLists.txt vendored Normal file
View File

@ -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)

1
3rdparty/cubeb/cubeb vendored Submodule

@ -0,0 +1 @@
Subproject commit d512bfa07a327e0ae7e7aef892dcce01cbeaa67c

3
3rdparty/cubeb/extra/cubeb_export.h vendored Normal file
View File

@ -0,0 +1,3 @@
#pragma once
#define CUBEB_EXPORT

87
3rdparty/cubeb/libcubeb.vcxproj vendored Normal file
View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="cubeb\src\cubeb.c" />
<ClCompile Include="cubeb\src\cubeb_log.cpp" />
<ClCompile Include="cubeb\src\cubeb_mixer.cpp" />
<ClCompile Include="cubeb\src\cubeb_resampler.cpp" />
<ClCompile Include="cubeb\src\cubeb_strings.c" />
<ClCompile Include="cubeb\src\cubeb_utils.cpp" />
<ClCompile Include="cubeb\src\cubeb_wasapi.cpp" />
<ClCompile Include="cubeb\src\cubeb_winmm.c" />
<ClCompile Include="cubeb\src\speex\resample.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="cubeb\include\cubeb\cubeb.h" />
<ClInclude Include="cubeb\src\cubeb-internal.h" />
<ClInclude Include="cubeb\src\cubeb-speex-resampler.h" />
<ClInclude Include="cubeb\src\cubeb_array_queue.h" />
<ClInclude Include="cubeb\src\cubeb_assert.h" />
<ClInclude Include="cubeb\src\cubeb_log.h" />
<ClInclude Include="cubeb\src\cubeb_mixer.h" />
<ClInclude Include="cubeb\src\cubeb_resampler.h" />
<ClInclude Include="cubeb\src\cubeb_resampler_internal.h" />
<ClInclude Include="cubeb\src\cubeb_ringbuffer.h" />
<ClInclude Include="cubeb\src\cubeb_ring_array.h" />
<ClInclude Include="cubeb\src\cubeb_strings.h" />
<ClInclude Include="cubeb\src\cubeb_utils.h" />
<ClInclude Include="cubeb\src\cubeb_utils_win.h" />
<ClInclude Include="cubeb\src\speex\arch.h" />
<ClInclude Include="cubeb\src\speex\fixed_generic.h" />
<ClInclude Include="cubeb\src\speex\resample_neon.h" />
<ClInclude Include="cubeb\src\speex\resample_sse.h" />
<ClInclude Include="cubeb\src\speex\speex_config_types.h" />
<ClInclude Include="cubeb\src\speex\speex_resampler.h" />
<ClInclude Include="cubeb\src\speex\stack_alloc.h" />
<ClInclude Include="extra\cubeb_export.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{fda7b080-03b0-48c8-b24f-88118981422a}</ProjectGuid>
<RootNamespace>libcubeb</RootNamespace>
</PropertyGroup>
<Import Project="$(SolutionDir)\buildfiles\msvc\common_default.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(SolutionDir)\buildfiles\msvc\common_default_macros.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>false</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_default.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_debug.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_release.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<SDLCheck>false</SDLCheck>
<PreprocessorDefinitions>OUTSIDE_SPEEX;RANDOM_PREFIX=speex;FLOATING_POINT;EXPORT=;USE_WASAPI;USE_WINMM;CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME;_HAS_DEPRECATED_RESULT_OF;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>./cubeb/src/speex/;./extra/;./cubeb/src/;./cubeb/include/;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

108
3rdparty/cubeb/libcubeb.vcxproj.filters vendored Normal file
View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="cubeb\src\speex\resample.c">
<Filter>Source files</Filter>
</ClCompile>
<ClCompile Include="cubeb\src\cubeb_winmm.c">
<Filter>Source files</Filter>
</ClCompile>
<ClCompile Include="cubeb\src\cubeb_utils.cpp">
<Filter>Source files</Filter>
</ClCompile>
<ClCompile Include="cubeb\src\cubeb_wasapi.cpp">
<Filter>Source files</Filter>
</ClCompile>
<ClCompile Include="cubeb\src\cubeb_strings.c">
<Filter>Source files</Filter>
</ClCompile>
<ClCompile Include="cubeb\src\cubeb_resampler.cpp">
<Filter>Source files</Filter>
</ClCompile>
<ClCompile Include="cubeb\src\cubeb_mixer.cpp">
<Filter>Source files</Filter>
</ClCompile>
<ClCompile Include="cubeb\src\cubeb_log.cpp">
<Filter>Source files</Filter>
</ClCompile>
<ClCompile Include="cubeb\src\cubeb.c">
<Filter>Source files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="cubeb\include\cubeb\cubeb.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\speex\arch.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb_array_queue.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb_assert.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="extra\cubeb_export.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb_log.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb_mixer.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb_resampler.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb_resampler_internal.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb_ring_array.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb_ringbuffer.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb_strings.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb_utils.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb_utils_win.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb-internal.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\cubeb-speex-resampler.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\speex\fixed_generic.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\speex\resample_neon.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\speex\resample_sse.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\speex\speex_config_types.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\speex\speex_resampler.h">
<Filter>Header files</Filter>
</ClInclude>
<ClInclude Include="cubeb\src\speex\stack_alloc.h">
<Filter>Header files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Source files">
<UniqueIdentifier>{cd86de6d-e811-4c48-9653-af00c7098a45}</UniqueIdentifier>
</Filter>
<Filter Include="Header files">
<UniqueIdentifier>{566bbf3e-ca28-4390-b054-58c92a66f24e}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>

View File

@ -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

View File

@ -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$<$<CONFIG:Debug>: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$<$<CONFIG:Debug>:Debug>DLL
# says if that property is not set then CMake uses the default value MultiThreaded$<$<CONFIG:Debug>: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.

View File

@ -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<u32>(_rw_ptr);
const u32 wr = static_cast<u32>(_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<u8[]>(this->buf_size);
flush();
initialized = true;
}
void simple_ringbuf::flush()
{
rw_ptr.atomic_op([&](u64 &val)
{
val = static_cast<u32>(val >> 32) | (val & 0xFFFFFFFF'00000000);
});
}
u32 simple_ringbuf::push(const void *data, u32 size)
{
ensure(data != nullptr && initialized.observe());
const u32 old = static_cast<u32>(rw_ptr.load() >> 32);
const u32 to_push = std::min(size, get_free_size());
auto b_data = static_cast<const u8*>(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<u64>((old + to_push) % buf_size) << 32 | static_cast<u32>(val);
});
return to_push;
}
u32 simple_ringbuf::pop(void *data, u32 size)
{
ensure(data != nullptr && initialized.observe());
const u32 old = static_cast<u32>(rw_ptr.load());
const u32 to_pop = std::min(size, get_used_size());
u8 *b_data = static_cast<u8*>(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;
}

View File

@ -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<u64> rw_ptr = 0;
u32 buf_size = 0;
std::unique_ptr<u8[]> buf{};
atomic_t<bool> 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);
};

View File

@ -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}

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
@ -11,9 +11,8 @@
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{30A05C4D-F5FD-421C-A864-17A64BDEAA75}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>OpenAL</RootNamespace>
<ProjectGuid>{9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}</ProjectGuid>
<RootNamespace>Cubeb</RootNamespace>
</PropertyGroup>
<Import Project="$(SolutionDir)\buildfiles\msvc\common_default.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
@ -37,36 +36,28 @@
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_debug.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'" Label="PropertySheets">
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_debug.props" />
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_llvm.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_release.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'" Label="PropertySheets">
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_release.props" />
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_llvm.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>.\3rdparty\OpenAL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\3rdparty\cubeb\cubeb\include\;..\3rdparty\cubeb\extra\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="Emu\Audio\AL\OpenALBackend.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Emu\Audio\AL\OpenALBackend.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="emucore.vcxproj">
<Project>{c4a10229-4712-4bd2-b63e-50d93c67a038}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Emu\Audio\Cubeb\CubebBackend.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="Emu\Audio\Cubeb\CubebBackend.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<UniqueIdentifier>{F573EFFC-2BB8-43DF-AEFA-4E9EA31A817F}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Emu\Audio\AL\OpenALBackend.h">
<Filter>Source Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Emu\Audio\AL\OpenALBackend.cpp">
<ClCompile Include="Emu\Audio\Cubeb\CubebBackend.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>
<ItemGroup>
<ClInclude Include="Emu\Audio\Cubeb\CubebBackend.h">
<Filter>Source Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -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<u32>(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;
}

View File

@ -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;
};

View File

@ -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;
}

View File

@ -1,33 +0,0 @@
#pragma once
#ifndef HAVE_ALSA
#error "ALSA support disabled but still being built."
#endif
#include "Emu/Audio/AudioBackend.h"
#include <alsa/asoundlib.h>
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;
};

View File

@ -2,67 +2,29 @@
#include "AudioBackend.h"
#include "Emu/system_config.h"
AudioBackend::AudioBackend()
{
m_convert_to_u16 = static_cast<bool>(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<u32>(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<int>(downmix));
}
}
AudioBackend::AudioBackend() {}
/*
* Helper methods
*/
u32 AudioBackend::get_sampling_rate() const
{
return m_sampling_rate;
return static_cast<std::underlying_type_t<decltype(m_sampling_rate)>>(m_sampling_rate);
}
u32 AudioBackend::get_sample_size() const
{
return m_sample_size;
return static_cast<std::underlying_type_t<decltype(m_sample_size)>>(m_sample_size);
}
u32 AudioBackend::get_channels() const
{
return m_channels;
return static_cast<std::underlying_type_t<decltype(m_channels)>>(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 ? " | " : "");

View File

@ -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<u32(u32 /* byte_cnt */, void * /* buffer */)> 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;
};

View File

@ -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())
{

View File

@ -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);

View File

@ -0,0 +1,215 @@
#include "Emu/Audio/Cubeb/CubebBackend.h"
#include <algorithm>
#include "util/logs.hpp"
#ifdef _WIN32
#include <Windows.h>
#include <system_error>
#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<u32>(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<u32(u32, void *)> 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<f64>(std::max<u32>(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<CubebBackend*>(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<u8*>(output_buffer) + written - sample_size, sample_size);
}
for (u32 i = written; i < bytes_req; i += sample_size)
{
memcpy(static_cast<u8*>(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<CubebBackend*>(user_ptr);
if (state == CUBEB_STATE_ERROR)
{
Cubeb.error("Stream entered error state");
cubeb->m_reset_req = true;
}
}

View File

@ -0,0 +1,56 @@
#pragma once
#include <memory>
#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<u32(u32, void *)> 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<u32(u32, void *)> m_write_callback{};
u8 m_last_sample[sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)]{};
bool m_playing = false;
atomic_t<bool> 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();
};

View File

@ -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<u32>(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<u32>(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<u32>(FAUDIO_DEFAULT_FREQ_RATIO) / 1000;
m_data_buf = std::make_unique<u8[]>(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<const u8*>(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<u32(u32, void *)> 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<f64>(samples_per_q) / freq;
}
return std::max<f64>(min_latency, _10ms);
}
void FAudioBackend::OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj, u32 BytesRequired)
{
FAudioBackend *faudio = static_cast<FAudioBackend *>(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<const u8*>(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<FAudioBackend *>(cb_obj);
faudio->m_reset_req = true;
}

View File

@ -4,16 +4,14 @@
#error "FAudio support disabled but still being built."
#endif
#include <memory>
#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<u32(u32, void *)> 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<u32(u32, void *)> m_write_callback{};
std::unique_ptr<u8[]> m_data_buf{};
u64 m_data_buf_len = 0;
u8 m_last_sample[sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)]{};
bool m_playing = false;
atomic_t<bool> 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();
};

View File

@ -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<u32(u32, void *)> /* 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;
};

View File

@ -1,88 +0,0 @@
#ifndef HAVE_PULSE
#error "PulseAudio support disabled but still being built."
#endif
#include "Emu/System.h"
#include "PulseBackend.h"
#include <pulse/simple.h>
#include <pulse/error.h>
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;
}

View File

@ -1,31 +0,0 @@
#pragma once
#ifndef HAVE_PULSE
#error "PulseAudio support disabled but still being built."
#endif
#include <pulse/simple.h>
#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{};
};

View File

@ -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<u32>(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<u32>(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<u32>(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<u32>(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<u32>(hr));
XAudio.error("Start() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(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<u32>(hr));
XAudio.error("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(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<DWORD>(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<u32>(hr));
XAudio.error("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
}
hr = m_source_voice->FlushSourceBuffers();
if (FAILED(hr))
{
XAudio.error("FlushSourceBuffers() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(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<DWORD>(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<u32>(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<u32>(XAUDIO2_DEFAULT_FREQ_RATIO) / 1000;
m_data_buf = std::make_unique<u8[]>(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<const BYTE*>(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<u32>(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<u32>(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<u64>(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<u32>(hr));
XAudio.error("SetFrequencyRatio() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
return 1.0f;
}
return new_ratio;
}
void XAudio2Backend::SetWriteCallback(std::function<u32(u32, void *)> 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<u32>(hr));
}
else
{
u32 samples_per_q = 0, freq = 0;
static_cast<IXAudio2Extension *>(ext)->GetProcessingQuantum(&samples_per_q, &freq);
if (freq)
{
min_latency = static_cast<f64>(samples_per_q) / freq;
}
}
return std::max<f64>(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<const BYTE*>(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<u32>(hr));
}
}
}
void XAudio2Backend::OnCriticalError(HRESULT Error)
{
XAudio.error("OnCriticalError() called: %s (0x%08x)", std::system_category().message(Error), static_cast<u32>(Error));
m_reset_req = true;
}

View File

@ -4,19 +4,16 @@
#error "XAudio2 can only be built on Windows."
#endif
#include <memory>
#include "Utilities/mutex.h"
#include "Emu/Audio/AudioBackend.h"
#include "Emu/Audio/audio_device_listener.h"
#include <xaudio2redist.h>
#include <wrl/client.h>
class XAudio2Backend final : public AudioBackend
class XAudio2Backend final : public AudioBackend, public IXAudio2VoiceCallback, public IXAudio2EngineCallback
{
private:
Microsoft::WRL::ComPtr<IXAudio2> 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<u32(u32, void *)> 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<IXAudio2> m_xaudio2_instance{};
IXAudio2MasteringVoice* m_master_voice{};
IXAudio2SourceVoice* m_source_voice{};
bool m_com_init_success = false;
shared_mutex m_cb_mutex{};
std::function<u32(u32, void *)> m_write_callback{};
std::unique_ptr<u8[]> m_data_buf{};
u64 m_data_buf_len = 0;
u8 m_last_sample[sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)]{};
bool m_playing = false;
atomic_t<bool> 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();
};

View File

@ -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<u32>(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<u32>(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<ERole>::format(std::string& out, u64 arg)
@ -70,7 +97,7 @@ void fmt_class_string<EDataFlow>::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<cell_audio>(); g_fxo->is_init<cell_audio>())
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;
}
}

View File

@ -4,28 +4,39 @@
#include <MMDeviceAPI.h>
#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<bool> m_input_device_changed = false;
atomic_t<bool> m_output_device_changed = false;
};

View File

@ -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

View File

@ -47,24 +47,52 @@ void fmt_class_string<CellAudioError>::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<int>(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<f64>(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<u32>(_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<u64>(play_timestamp, update_timestamp) : 0);
const u64 delta_samples_tmp = play_delta * static_cast<u64>(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<bool>(g_cfg.audio.enable_time_stretching),
.time_stretching_threshold = g_cfg.audio.time_stretching_threshold,
.convert_to_u16 = static_cast<bool>(g_cfg.audio.convert_to_u16),
.start_threshold = static_cast<u32>(g_cfg.audio.start_threshold),
.sampling_period_multiplier = static_cast<u32>(g_cfg.audio.sampling_period_multiplier),
.convert_to_s16 = static_cast<bool>(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<cell_audio>(); g_fxo->is_init<cell_audio>())
{
// 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)

View File

@ -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<AudioBackend> 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<float[]> 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<bool> 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<audio_ringbuffer> 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<bool> m_update_configuration = false;
atomic_t<audio_backend_update> m_update_configuration = audio_backend_update::NONE;
shared_mutex mutex;
atomic_t<u32> 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()();

View File

@ -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<u32 Size>
class vuart_av_ringbuf
{
private:
atomic_t<u32> rd_ptr = 0;
atomic_t<u32> 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<const u8*>(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<u8*>(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<PS3AV_TX_BUF_SIZE> tx_buf{};
vuart_av_ringbuf<PS3AV_RX_BUF_SIZE> 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);

View File

@ -213,23 +213,14 @@ struct cfg_root : cfg::node
{
node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {}
cfg::_enum<audio_renderer> renderer{ this, "Renderer",
#ifdef _WIN32
audio_renderer::xaudio, true };
#elif HAVE_FAUDIO
audio_renderer::faudio, true };
#else
audio_renderer::openal, true };
#endif
cfg::_enum<audio_renderer> renderer{ this, "Renderer", audio_renderer::cubeb, true };
cfg::_enum<audio_provider> 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_downmix> 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_handler> microphone_type{ this, "Microphone Type", microphone_handler::null };

View File

@ -109,13 +109,7 @@ void fmt_class_string<audio_renderer>::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<shader_mode>::format(std::string& out, u64 arg)
});
}
template <>
void fmt_class_string<audio_provider>::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<audio_downmix>::format(std::string& out, u64 arg)
{

View File

@ -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

View File

@ -52,16 +52,10 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\Utilities\cheat_info.cpp" />
<ClCompile Include="Emu\Audio\ALSA\ALSABackend.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="Emu\Audio\audio_device_listener.cpp" />
<ClCompile Include="Emu\Audio\FAudio\FAudioBackend.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="Emu\Audio\Pulse\PulseBackend.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="Emu\cache_utils.cpp" />
<ClCompile Include="Emu\Cell\Modules\libfs_utility_init.cpp" />
<ClCompile Include="Emu\Cell\Modules\sys_crashdump.cpp" />
@ -142,6 +136,9 @@
<ClCompile Include="..\Utilities\sema.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Utilities\simple_ringbuf.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Utilities\StrFmt.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
@ -436,16 +433,10 @@
<ClInclude Include="..\3rdparty\stblib\include\stb_image.h" />
<ClInclude Include="..\Utilities\address_range.h" />
<ClInclude Include="..\Utilities\cheat_info.h" />
<ClInclude Include="Emu\Audio\ALSA\ALSABackend.h">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="Emu\Audio\audio_device_listener.h" />
<ClInclude Include="Emu\Audio\FAudio\FAudioBackend.h">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="Emu\Audio\Pulse\PulseBackend.h">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="Emu\cache_utils.hpp" />
<ClInclude Include="Emu\Cell\lv2\sys_crypto_engine.h" />
<ClInclude Include="Emu\Cell\Modules\cellCrossController.h" />
@ -797,4 +788,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -70,12 +70,6 @@
<Filter Include="Emu\Audio\FAudio">
<UniqueIdentifier>{7555ff6f-67a9-4d02-b744-0bf896751edb}</UniqueIdentifier>
</Filter>
<Filter Include="Emu\Audio\ALSA">
<UniqueIdentifier>{c76057f1-bb19-4981-8ab0-5ba20ac27552}</UniqueIdentifier>
</Filter>
<Filter Include="Emu\Audio\Pulse">
<UniqueIdentifier>{84c0e40e-efb6-4cfd-b2f5-13c720b09105}</UniqueIdentifier>
</Filter>
<Filter Include="Emu\GPU\RSX\Program">
<UniqueIdentifier>{d055ca32-157a-4d8c-895e-29509858fcb0}</UniqueIdentifier>
</Filter>
@ -141,6 +135,9 @@
<ClCompile Include="Loader\TRP.cpp">
<Filter>Loader</Filter>
</ClCompile>
<ClCompile Include="..\Utilities\simple_ringbuf.cpp">
<Filter>Utilities</Filter>
</ClCompile>
<ClCompile Include="..\Utilities\StrFmt.cpp">
<Filter>Utilities</Filter>
</ClCompile>
@ -924,12 +921,6 @@
<ClCompile Include="Emu\Audio\FAudio\FAudioBackend.cpp">
<Filter>Emu\Audio\FAudio</Filter>
</ClCompile>
<ClCompile Include="Emu\Audio\ALSA\ALSABackend.cpp">
<Filter>Emu\Audio\ALSA</Filter>
</ClCompile>
<ClCompile Include="Emu\Audio\Pulse\PulseBackend.cpp">
<Filter>Emu\Audio\Pulse</Filter>
</ClCompile>
<ClCompile Include="..\Utilities\cheat_info.cpp">
<Filter>Utilities</Filter>
</ClCompile>
@ -1884,12 +1875,6 @@
<ClInclude Include="Emu\Audio\FAudio\FAudioBackend.h">
<Filter>Emu\Audio\FAudio</Filter>
</ClInclude>
<ClInclude Include="Emu\Audio\ALSA\ALSABackend.h">
<Filter>Emu\Audio\ALSA</Filter>
</ClInclude>
<ClInclude Include="Emu\Audio\Pulse\PulseBackend.h">
<Filter>Emu\Audio\Pulse</Filter>
</ClInclude>
<ClInclude Include="..\Utilities\cheat_info.h">
<Filter>Utilities</Filter>
</ClInclude>
@ -2038,4 +2023,4 @@
<Filter>Emu\GPU\RSX\Common\Interpreter</Filter>
</None>
</ItemGroup>
</Project>
</Project>

View File

@ -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<XAudio2Backend>(); break;
#endif
#ifdef HAVE_ALSA
case audio_renderer::alsa: result = std::make_shared<ALSABackend>(); break;
#endif
#ifdef HAVE_PULSE
case audio_renderer::pulse: result = std::make_shared<PulseBackend>(); break;
#endif
case audio_renderer::openal: result = std::make_shared<OpenALBackend>(); break;
case audio_renderer::cubeb: result = std::make_shared<CubebBackend>(); break;
#ifdef HAVE_FAUDIO
case audio_renderer::faudio: result = std::make_shared<FAudioBackend>(); break;
#endif

View File

@ -71,7 +71,7 @@
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<AdditionalOptions>-Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
<AssemblerListingLocation>release\</AssemblerListingLocation>
<BrowseInformation>false</BrowseInformation>
@ -88,7 +88,7 @@
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<AdditionalDependencies>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)</AdditionalDependencies>
<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)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\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</AdditionalLibraryDirectories>
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
<DataExecutionPrevention>true</DataExecutionPrevention>
@ -122,7 +122,7 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<AdditionalOptions>-Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
<AssemblerListingLocation>debug\</AssemblerListingLocation>
<BrowseInformation>false</BrowseInformation>
@ -139,7 +139,7 @@
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<AdditionalDependencies>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)</AdditionalDependencies>
<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)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\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</AdditionalLibraryDirectories>
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /VERBOSE %(AdditionalOptions)</AdditionalOptions>
<DataExecutionPrevention>true</DataExecutionPrevention>

View File

@ -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

View File

@ -479,7 +479,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> 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<int>(zcull_precision_level::precise));
ui->zcullPrecisionMode->addItem(tr("Approximate (Fast)"), static_cast<int>(zcull_precision_level::approximate));
@ -822,7 +822,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
const QVariantList var_list = ui->audioOutBox->itemData(index).toList();
ensure(var_list.size() == 2 && var_list[0].canConvert<QString>());
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());
};

View File

@ -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.");