mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-21 18:22:33 +01: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:
parent
a84223bdc6
commit
37a722cc1d
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -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
|
||||
|
36
3rdparty/CMakeLists.txt
vendored
36
3rdparty/CMakeLists.txt
vendored
@ -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
12
3rdparty/cubeb/CMakeLists.txt
vendored
Normal 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
1
3rdparty/cubeb/cubeb
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit d512bfa07a327e0ae7e7aef892dcce01cbeaa67c
|
3
3rdparty/cubeb/extra/cubeb_export.h
vendored
Normal file
3
3rdparty/cubeb/extra/cubeb_export.h
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define CUBEB_EXPORT
|
87
3rdparty/cubeb/libcubeb.vcxproj
vendored
Normal file
87
3rdparty/cubeb/libcubeb.vcxproj
vendored
Normal 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
108
3rdparty/cubeb/libcubeb.vcxproj.filters
vendored
Normal 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>
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
96
Utilities/simple_ringbuf.cpp
Normal file
96
Utilities/simple_ringbuf.cpp
Normal 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;
|
||||
}
|
31
Utilities/simple_ringbuf.h
Normal file
31
Utilities/simple_ringbuf.h
Normal 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);
|
||||
};
|
24
rpcs3.sln
24
rpcs3.sln
@ -17,8 +17,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rpcs3.emu", "rpcs3.emu", "{
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XAudio", "rpcs3\XAudio.vcxproj", "{78CB2F39-B809-4A06-8329-8C0A19119D3D}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenAL", "rpcs3\OpenAL.vcxproj", "{30A05C4D-F5FD-421C-A864-17A64BDEAA75}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pnglibconf", "3rdparty\libpng\pnglibconf.vcxproj", "{EB33566E-DA7F-4D28-9077-88C0B7C77E35}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpng", "3rdparty\libpng\libpng.vcxproj", "{D6973076-9317-4EF2-A0B8-B7A18AC0713E}"
|
||||
@ -47,10 +45,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rpcs3", "rpcs3\rpcs3.vcxpro
|
||||
{C4A10229-4712-4BD2-B63E-50D93C67A038} = {C4A10229-4712-4BD2-B63E-50D93C67A038}
|
||||
{78CB2F39-B809-4A06-8329-8C0A19119D3D} = {78CB2F39-B809-4A06-8329-8C0A19119D3D}
|
||||
{3384223A-6D97-4799-9862-359F85312892} = {3384223A-6D97-4799-9862-359F85312892}
|
||||
{30A05C4D-F5FD-421C-A864-17A64BDEAA75} = {30A05C4D-F5FD-421C-A864-17A64BDEAA75}
|
||||
{60F89955-91C6-3A36-8000-13C592FEC2DF} = {60F89955-91C6-3A36-8000-13C592FEC2DF}
|
||||
{3EE5F075-B546-42C4-B6A8-E3CCEF38B78D} = {3EE5F075-B546-42C4-B6A8-E3CCEF38B78D}
|
||||
{D6973076-9317-4EF2-A0B8-B7A18AC0713E} = {D6973076-9317-4EF2-A0B8-B7A18AC0713E}
|
||||
{9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} = {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}
|
||||
{FDA7B080-03B0-48C8-B24F-88118981422A} = {FDA7B080-03B0-48C8-B24F-88118981422A}
|
||||
{DA6F56B4-06A4-441D-AD70-AC5A7D51FADB} = {DA6F56B4-06A4-441D-AD70-AC5A7D51FADB}
|
||||
{FDC361C5-7734-493B-8CFB-037308B35122} = {FDC361C5-7734-493B-8CFB-037308B35122}
|
||||
{8F85B6CC-250F-4ACA-A617-E820A74E3E3C} = {8F85B6CC-250F-4ACA-A617-E820A74E3E3C}
|
||||
@ -79,6 +78,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3rdParty", "3rdParty", "{6C
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "llvm_build_clang_cl", "llvm_build\llvm_build_clang_cl.vcxproj", "{A37E4273-85DB-4217-B775-CE971B87D9DF}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Cubeb", "rpcs3\Cubeb.vcxproj", "{9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcubeb", "3rdparty\cubeb\libcubeb.vcxproj", "{FDA7B080-03B0-48C8-B24F-88118981422A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
@ -103,10 +106,6 @@ Global
|
||||
{78CB2F39-B809-4A06-8329-8C0A19119D3D}.Debug|x64.Build.0 = Debug|x64
|
||||
{78CB2F39-B809-4A06-8329-8C0A19119D3D}.Release|x64.ActiveCfg = Release|x64
|
||||
{78CB2F39-B809-4A06-8329-8C0A19119D3D}.Release|x64.Build.0 = Release|x64
|
||||
{30A05C4D-F5FD-421C-A864-17A64BDEAA75}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{30A05C4D-F5FD-421C-A864-17A64BDEAA75}.Debug|x64.Build.0 = Debug|x64
|
||||
{30A05C4D-F5FD-421C-A864-17A64BDEAA75}.Release|x64.ActiveCfg = Release|x64
|
||||
{30A05C4D-F5FD-421C-A864-17A64BDEAA75}.Release|x64.Build.0 = Release|x64
|
||||
{EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug|x64.ActiveCfg = Release|x64
|
||||
{EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug|x64.Build.0 = Release|x64
|
||||
{EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release|x64.ActiveCfg = Release|x64
|
||||
@ -161,6 +160,14 @@ Global
|
||||
{4CBD3DDD-5555-49A4-A44D-DD3D8CB516A1}.Release|x64.ActiveCfg = Release|x64
|
||||
{A37E4273-85DB-4217-B775-CE971B87D9DF}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A37E4273-85DB-4217-B775-CE971B87D9DF}.Release|x64.ActiveCfg = Release|x64
|
||||
{9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}.Debug|x64.Build.0 = Debug|x64
|
||||
{9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}.Release|x64.ActiveCfg = Release|x64
|
||||
{9610627D-20FE-4B07-8CE3-9FF68A5F1EC2}.Release|x64.Build.0 = Release|x64
|
||||
{FDA7B080-03B0-48C8-B24F-88118981422A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{FDA7B080-03B0-48C8-B24F-88118981422A}.Debug|x64.Build.0 = Debug|x64
|
||||
{FDA7B080-03B0-48C8-B24F-88118981422A}.Release|x64.ActiveCfg = Release|x64
|
||||
{FDA7B080-03B0-48C8-B24F-88118981422A}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -171,7 +178,6 @@ Global
|
||||
{8BC303AB-25BE-4276-8E57-73F171B2D672} = {B0AC29FD-7B01-4B5E-9C8D-0A081E4C5668}
|
||||
{3384223A-6D97-4799-9862-359F85312892} = {10FBF193-D532-4CCF-B875-4C7091A7F6C2}
|
||||
{78CB2F39-B809-4A06-8329-8C0A19119D3D} = {10FBF193-D532-4CCF-B875-4C7091A7F6C2}
|
||||
{30A05C4D-F5FD-421C-A864-17A64BDEAA75} = {10FBF193-D532-4CCF-B875-4C7091A7F6C2}
|
||||
{EB33566E-DA7F-4D28-9077-88C0B7C77E35} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8}
|
||||
{D6973076-9317-4EF2-A0B8-B7A18AC0713E} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8}
|
||||
{60F89955-91C6-3A36-8000-13C592FEC2DF} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8}
|
||||
@ -186,6 +192,8 @@ Global
|
||||
{DA6F56B4-06A4-441D-AD70-AC5A7D51FADB} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8}
|
||||
{4CBD3DDD-5555-49A4-A44D-DD3D8CB516A1} = {B0AC29FD-7B01-4B5E-9C8D-0A081E4C5668}
|
||||
{A37E4273-85DB-4217-B775-CE971B87D9DF} = {B0AC29FD-7B01-4B5E-9C8D-0A081E4C5668}
|
||||
{9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} = {10FBF193-D532-4CCF-B875-4C7091A7F6C2}
|
||||
{FDA7B080-03B0-48C8-B24F-88118981422A} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {06CC7920-E085-4B81-9582-8DE8AAD42510}
|
||||
|
@ -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>
|
@ -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>
|
@ -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;
|
||||
}
|
@ -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;
|
||||
};
|
@ -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;
|
||||
}
|
@ -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;
|
||||
};
|
@ -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 ? " | " : "");
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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())
|
||||
{
|
||||
|
@ -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);
|
||||
|
215
rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp
Normal file
215
rpcs3/Emu/Audio/Cubeb/CubebBackend.cpp
Normal 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;
|
||||
}
|
||||
}
|
56
rpcs3/Emu/Audio/Cubeb/CubebBackend.h
Normal file
56
rpcs3/Emu/Audio/Cubeb/CubebBackend.h
Normal 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();
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
@ -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{};
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 };
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
};
|
||||
|
@ -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.");
|
||||
|
Loading…
Reference in New Issue
Block a user