Merge branch 'master' into mipmap

This commit is contained in:
Feng Chen 2022-09-20 11:56:43 +08:00 committed by GitHub
commit c864cb5772
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
185 changed files with 3156 additions and 1821 deletions

View file

@ -5,21 +5,24 @@
. .ci/scripts/common/pre-upload.sh . .ci/scripts/common/pre-upload.sh
APPIMAGE_NAME="yuzu-${GITDATE}-${GITREV}.AppImage" APPIMAGE_NAME="yuzu-${RELEASE_NAME}-${GITDATE}-${GITREV}.AppImage"
REV_NAME="yuzu-linux-${GITDATE}-${GITREV}" BASE_NAME="yuzu-linux"
REV_NAME="${BASE_NAME}-${GITDATE}-${GITREV}"
ARCHIVE_NAME="${REV_NAME}.tar.xz" ARCHIVE_NAME="${REV_NAME}.tar.xz"
COMPRESSION_FLAGS="-cJvf" COMPRESSION_FLAGS="-cJvf"
if [ "${RELEASE_NAME}" = "mainline" ]; then if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
DIR_NAME="${REV_NAME}" DIR_NAME="${BASE_NAME}-${RELEASE_NAME}"
else else
DIR_NAME="${REV_NAME}_${RELEASE_NAME}" DIR_NAME="${REV_NAME}-${RELEASE_NAME}"
fi fi
mkdir "$DIR_NAME" mkdir "$DIR_NAME"
cp build/bin/yuzu-cmd "$DIR_NAME" cp build/bin/yuzu-cmd "$DIR_NAME"
cp build/bin/yuzu "$DIR_NAME" if [ "${RELEASE_NAME}" != "early-access" ] && [ "${RELEASE_NAME}" != "mainline" ]; then
cp build/bin/yuzu "$DIR_NAME"
fi
# Build an AppImage # Build an AppImage
cd build cd build
@ -32,6 +35,11 @@ if ! ./appimagetool-x86_64.AppImage --version; then
export APPIMAGE_EXTRACT_AND_RUN=1 export APPIMAGE_EXTRACT_AND_RUN=1
fi fi
# Don't let AppImageLauncher ask to integrate EA
if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
echo "X-AppImage-Integrate=false" >> AppDir/org.yuzu_emu.yuzu.desktop
fi
if [ "${RELEASE_NAME}" = "mainline" ]; then if [ "${RELEASE_NAME}" = "mainline" ]; then
# Generate update information if releasing to mainline # Generate update information if releasing to mainline
./appimagetool-x86_64.AppImage -u "gh-releases-zsync|yuzu-emu|yuzu-${RELEASE_NAME}|latest|yuzu-*.AppImage.zsync" AppDir "${APPIMAGE_NAME}" ./appimagetool-x86_64.AppImage -u "gh-releases-zsync|yuzu-emu|yuzu-${RELEASE_NAME}|latest|yuzu-*.AppImage.zsync" AppDir "${APPIMAGE_NAME}"
@ -46,4 +54,9 @@ if [ -f "build/${APPIMAGE_NAME}.zsync" ]; then
cp "build/${APPIMAGE_NAME}.zsync" "${ARTIFACTS_DIR}/" cp "build/${APPIMAGE_NAME}.zsync" "${ARTIFACTS_DIR}/"
fi fi
# Copy the AppImage to the general release directory and remove git revision info
if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage"
fi
. .ci/scripts/common/post-upload.sh . .ci/scripts/common/post-upload.sh

View file

@ -21,6 +21,7 @@ cmake .. \
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
-DENABLE_QT_TRANSLATION=ON \ -DENABLE_QT_TRANSLATION=ON \
-DUSE_CCACHE=ON \ -DUSE_CCACHE=ON \
-DYUZU_CRASH_DUMPS=ON \
-DYUZU_USE_BUNDLED_SDL2=OFF \ -DYUZU_USE_BUNDLED_SDL2=OFF \
-DYUZU_USE_EXTERNAL_SDL2=OFF \ -DYUZU_USE_EXTERNAL_SDL2=OFF \
-DYUZU_TESTS=OFF \ -DYUZU_TESTS=OFF \

View file

@ -65,8 +65,8 @@ if ("$env:GITHUB_ACTIONS" -eq "true") {
# None of the other GHA builds are including source, so commenting out today # None of the other GHA builds are including source, so commenting out today
#Copy-Item $MSVC_SOURCE_TARXZ -Destination "artifacts" #Copy-Item $MSVC_SOURCE_TARXZ -Destination "artifacts"
# Are debug symbols important? # Debugging symbols
# cp .\build\bin\yuzu*.pdb .\pdb\ cp .\build\bin\yuzu*.pdb .\artifacts\
# Write out a tag BUILD_TAG to environment for the Upload step # Write out a tag BUILD_TAG to environment for the Upload step
# We're getting ${{ github.event.number }} as $env:PR_NUMBER" # We're getting ${{ github.event.number }} as $env:PR_NUMBER"

View file

@ -9,7 +9,7 @@ parameters:
steps: steps:
- script: choco install vulkan-sdk - script: choco install vulkan-sdk
displayName: 'Install vulkan-sdk' displayName: 'Install vulkan-sdk'
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release .. && cd .. - script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd ..
displayName: 'Configure CMake' displayName: 'Configure CMake'
- task: MSBuild@1 - task: MSBuild@1
displayName: 'Build' displayName: 'Build'

View file

@ -50,7 +50,7 @@ stages:
timeoutInMinutes: 120 timeoutInMinutes: 120
displayName: 'msvc' displayName: 'msvc'
pool: pool:
vmImage: windows-2019 vmImage: windows-2022
steps: steps:
- template: ./templates/sync-source.yml - template: ./templates/sync-source.yml
parameters: parameters:

View file

@ -11,11 +11,32 @@ stages:
- stage: build - stage: build
displayName: 'build' displayName: 'build'
jobs: jobs:
- job: build - job: linux
timeoutInMinutes: 120 timeoutInMinutes: 120
displayName: 'windows-msvc' displayName: 'linux'
pool: pool:
vmImage: windows-2019 vmImage: ubuntu-latest
strategy:
maxParallel: 10
matrix:
linux:
BuildSuffix: 'linux'
ScriptFolder: 'linux'
steps:
- template: ./templates/sync-source.yml
parameters:
artifactSource: $(parameters.artifactSource)
needSubmodules: 'true'
- template: ./templates/build-single.yml
parameters:
artifactSource: 'false'
cache: $(parameters.cache)
version: $(DisplayVersion)
- job: msvc
timeoutInMinutes: 120
displayName: 'windows'
pool:
vmImage: windows-2022
steps: steps:
- template: ./templates/sync-source.yml - template: ./templates/sync-source.yml
parameters: parameters:

View file

@ -71,7 +71,7 @@ jobs:
build-msvc: build-msvc:
name: 'test build (windows, msvc)' name: 'test build (windows, msvc)'
needs: format needs: format
runs-on: windows-2019 runs-on: windows-2022
steps: steps:
- name: Set up cache - name: Set up cache
uses: actions/cache@v3 uses: actions/cache@v3
@ -104,7 +104,7 @@ jobs:
run: | run: |
glslangValidator --version glslangValidator --version
mkdir build mkdir build
cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify -DYUZU_CRASH_DUMPS=ON
- name: Build - name: Build
run: cmake --build build run: cmake --build build
- name: Cache Summary - name: Cache Summary

2
.gitignore vendored
View file

@ -2,7 +2,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
# Build directory # Build directory
[Bb]uild/ [Bb]uild*/
doc-build/ doc-build/
# Generated source files # Generated source files

View file

@ -6,6 +6,7 @@ Files: dist/english_plurals/*
dist/icons/controller/*.png dist/icons/controller/*.png
dist/icons/overlay/*.png dist/icons/overlay/*.png
dist/languages/* dist/languages/*
dist/qt_themes/*/icons/48x48/sd_card.png
dist/qt_themes/*/icons/index.theme dist/qt_themes/*/icons/index.theme
dist/qt_themes/default/style.qss dist/qt_themes/default/style.qss
Copyright: yuzu Emulator Project Copyright: yuzu Emulator Project
@ -51,6 +52,8 @@ Files: dist/qt_themes/colorful/icons/16x16/lock.png
dist/qt_themes/colorful/icons/48x48/chip.png dist/qt_themes/colorful/icons/48x48/chip.png
dist/qt_themes/colorful/icons/48x48/folder.png dist/qt_themes/colorful/icons/48x48/folder.png
dist/qt_themes/colorful_dark/icons/16x16/lock.png dist/qt_themes/colorful_dark/icons/16x16/lock.png
dist/qt_themes/colorful/icons/16x16/info.png
dist/qt_themes/colorful/icons/16x16/sync.png
Copyright: Icons8 Copyright: Icons8
License: MIT License: MIT
Comment: https://github.com/icons8/flat-color-icons Comment: https://github.com/icons8/flat-color-icons
@ -66,11 +69,9 @@ Files: dist/qt_themes/*/icons/48x48/no_avatar.png
Copyright: Ionic (http://ionic.io/) Copyright: Ionic (http://ionic.io/)
License: MIT License: MIT
Files: dist/qt_themes/colorful/icons/48x48/star.png
Files: dist/qt_themes/*/icons/48x48/sd_card.png dist/qt_themes/colorful/icons/16x16/checked.png
dist/qt_themes/colorful/icons/48x48/star.png dist/qt_themes/colorful/icons/16x16/failed.png
dist/qt_themes/default/icons/16x16/checked.png
dist/qt_themes/default/icons/16x16/failed.png
Copyright: SVG Repo Copyright: SVG Repo
License: CC0-1.0 License: CC0-1.0

View file

@ -38,6 +38,8 @@ option(YUZU_USE_BUNDLED_OPUS "Compile bundled opus" ON)
option(YUZU_TESTS "Compile tests" ON) option(YUZU_TESTS "Compile tests" ON)
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}") option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}")
option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON) option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON)
@ -46,6 +48,9 @@ if (YUZU_USE_BUNDLED_VCPKG)
if (YUZU_TESTS) if (YUZU_TESTS)
list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests") list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
endif() endif()
if (YUZU_CRASH_DUMPS)
list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp")
endif()
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake) include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "") elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
@ -447,6 +452,13 @@ elseif (WIN32)
# PSAPI is the Process Status API # PSAPI is the Process Status API
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version) set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
endif() endif()
if (YUZU_CRASH_DUMPS)
find_library(DBGHELP_LIBRARY dbghelp)
if ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")
message(FATAL_ERROR "YUZU_CRASH_DUMPS enabled but dbghelp library not found")
endif()
endif()
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$") elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
set(PLATFORM_LIBRARIES rt) set(PLATFORM_LIBRARIES rt)
endif() endif()

View file

Before

Width:  |  Height:  |  Size: 414 B

After

Width:  |  Height:  |  Size: 414 B

View file

Before

Width:  |  Height:  |  Size: 431 B

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

View file

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 349 B

View file

Before

Width:  |  Height:  |  Size: 678 B

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 981 B

After

Width:  |  Height:  |  Size: 228 B

View file

@ -1,7 +1,6 @@
[Icon Theme] [Icon Theme]
Name=colorful Name=colorful
Comment=Colorful theme Comment=Colorful theme
Inherits=default
Directories=16x16,48x48,256x256 Directories=16x16,48x48,256x256
[16x16] [16x16]

View file

@ -6,14 +6,20 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RCC> <RCC>
<qresource prefix="icons/colorful"> <qresource prefix="icons/colorful">
<file alias="index.theme">icons/index.theme</file> <file alias="index.theme">icons/index.theme</file>
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
<file alias="16x16/connected.png">icons/16x16/connected.png</file> <file alias="16x16/connected.png">icons/16x16/connected.png</file>
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file> <file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file> <file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
<file alias="16x16/info.png">icons/16x16/info.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file> <file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="16x16/sync.png">icons/16x16/sync.png</file>
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file> <file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file> <file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file> <file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file> <file alias="48x48/star.png">icons/48x48/star.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>

View file

@ -5,19 +5,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RCC> <RCC>
<qresource prefix="icons/colorful_dark"> <qresource prefix="icons/colorful_dark">
<file alias="16x16/connected.png">../colorful/icons/16x16/connected.png</file>
<file alias="16x16/connected_notification.png">../colorful/icons/16x16/connected_notification.png</file>
<file alias="16x16/disconnected.png">../colorful/icons/16x16/disconnected.png</file>
<file alias="index.theme">icons/index.theme</file> <file alias="index.theme">icons/index.theme</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file> <file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file> <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
<file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
</qresource> </qresource>
<qresource prefix="qss_icons"> <qresource prefix="qss_icons">

View file

@ -5,23 +5,20 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RCC> <RCC>
<qresource prefix="icons/default"> <qresource prefix="icons/default">
<!-- "colorful" is now the default theme, add new icons there -->
<file alias="index.theme">icons/index.theme</file> <file alias="index.theme">icons/index.theme</file>
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="16x16/connected.png">icons/16x16/connected.png</file> <file alias="16x16/connected.png">icons/16x16/connected.png</file>
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file> <file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file> <file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file> <file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file> <file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file> <file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file> <file alias="48x48/star.png">icons/48x48/star.png</file>
<file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
<file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
</qresource> </qresource>
<qresource prefix="default"> <qresource prefix="default">
<file>style.qss</file> <file>style.qss</file>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 198 B

View file

@ -1,6 +1,7 @@
[Icon Theme] [Icon Theme]
Name=default Name=default
Comment=default theme Comment=default theme
Inherits=colorful
Directories=16x16,48x48,256x256 Directories=16x16,48x48,256x256
[16x16] [16x16]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 587 B

After

Width:  |  Height:  |  Size: 214 B

View file

@ -1,7 +1,7 @@
[Icon Theme] [Icon Theme]
Name=qdarkstyle Name=qdarkstyle
Comment=dark theme Comment=dark theme
Inherits=default Inherits=colorful
Directories=16x16,48x48,256x256 Directories=16x16,48x48,256x256
[16x16] [16x16]

View file

@ -1,7 +1,7 @@
[Icon Theme] [Icon Theme]
Name=qdarkstyle_midnight_blue Name=qdarkstyle_midnight_blue
Comment=dark theme Comment=dark theme
Inherits=default Inherits=colorful
Directories=16x16,48x48,256x256 Directories=16x16,48x48,256x256
[16x16] [16x16]

View file

@ -16,7 +16,6 @@ endif()
# Dynarmic # Dynarmic
if (ARCHITECTURE_x86_64) if (ARCHITECTURE_x86_64)
set(DYNARMIC_TESTS OFF)
set(DYNARMIC_NO_BUNDLED_FMT ON) set(DYNARMIC_NO_BUNDLED_FMT ON)
set(DYNARMIC_IGNORE_ASSERTS ON CACHE BOOL "" FORCE) set(DYNARMIC_IGNORE_ASSERTS ON CACHE BOOL "" FORCE)
add_subdirectory(dynarmic) add_subdirectory(dynarmic)

View file

@ -194,6 +194,7 @@ add_library(audio_core STATIC
sink/sink.h sink/sink.h
sink/sink_details.cpp sink/sink_details.cpp
sink/sink_details.h sink/sink_details.h
sink/sink_stream.cpp
sink/sink_stream.h sink/sink_stream.h
) )

View file

@ -47,22 +47,12 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
return *adsp; return *adsp;
} }
void AudioCore::PauseSinks(const bool pausing) const { void AudioCore::SetNVDECActive(bool active) {
if (pausing) { nvdec_active = active;
output_sink->PauseStreams();
input_sink->PauseStreams();
} else {
output_sink->UnpauseStreams();
input_sink->UnpauseStreams();
}
} }
u32 AudioCore::GetStreamQueue() const { bool AudioCore::IsNVDECActive() const {
return estimated_queue.load(); return nvdec_active;
}
void AudioCore::SetStreamQueue(u32 size) {
estimated_queue.store(size);
} }
} // namespace AudioCore } // namespace AudioCore

View file

@ -17,7 +17,7 @@ namespace AudioCore {
class AudioManager; class AudioManager;
/** /**
* Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP. * Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP.
*/ */
class AudioCore { class AudioCore {
public: public:
@ -58,26 +58,16 @@ public:
AudioRenderer::ADSP::ADSP& GetADSP(); AudioRenderer::ADSP::ADSP& GetADSP();
/** /**
* Pause the sink. Called from the core. * Toggle NVDEC state, used to avoid stall in playback.
* *
* @param pausing - Is this pause due to an actual pause, or shutdown? * @param active - Set true if nvdec is active, otherwise false.
* Unfortunately, shutdown also pauses streams, which can cause issues.
*/ */
void PauseSinks(bool pausing) const; void SetNVDECActive(bool active);
/** /**
* Get the size of the current stream queue. * Get NVDEC state.
*
* @return Current stream queue size.
*/ */
u32 GetStreamQueue() const; bool IsNVDECActive() const;
/**
* Get the size of the current stream queue.
*
* @param size - New stream size.
*/
void SetStreamQueue(u32 size);
private: private:
/** /**
@ -93,8 +83,8 @@ private:
std::unique_ptr<Sink::Sink> input_sink; std::unique_ptr<Sink::Sink> input_sink;
/// The ADSP in the sysmodule /// The ADSP in the sysmodule
std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
/// Current size of the stream queue /// Is NVDec currently active?
std::atomic<u32> estimated_queue{0}; bool nvdec_active{false};
}; };
} // namespace AudioCore } // namespace AudioCore

View file

@ -14,7 +14,7 @@ namespace AudioCore {
* Responsible for the input/output events, set by the stream backend when buffers are consumed, and * Responsible for the input/output events, set by the stream backend when buffers are consumed, and
* waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer
* recycling going. * recycling going.
* In a real Switch this is not a seprate class, and exists entirely within the audio manager. * In a real Switch this is not a separate class, and exists entirely within the audio manager.
* On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can
* wait on multiple events at once, and the events are not needed by the backend. * wait on multiple events at once, and the events are not needed by the backend.
*/ */
@ -81,7 +81,7 @@ public:
void ClearEvents(); void ClearEvents();
private: private:
/// Lock, used bythe audio manager /// Lock, used by the audio manager
std::mutex event_lock; std::mutex event_lock;
/// Array of events, one per system type (see Type), last event is used to terminate /// Array of events, one per system type (see Type), last event is used to terminate
std::array<std::atomic<bool>, 4> events_signalled; std::array<std::atomic<bool>, 4> events_signalled;

View file

@ -82,7 +82,7 @@ u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceN
auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)}; auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
if (input_devices.size() > 1) { if (input_devices.size() > 1) {
names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac")); names.emplace_back("Uac");
return 1; return 1;
} }
return 0; return 0;

View file

@ -59,9 +59,10 @@ public:
/** /**
* Get a list of audio in device names. * Get a list of audio in device names.
* *
* @oaram names - Output container to write names to. * @param names - Output container to write names to.
* @param max_count - Maximum numebr of deivce names to write. Unused * @param max_count - Maximum number of device names to write. Unused
* @param filter - Should the list be filtered? Unused. * @param filter - Should the list be filtered? Unused.
*
* @return Number of names written. * @return Number of names written.
*/ */
u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,

View file

@ -76,7 +76,7 @@ public:
private: private:
/** /**
* Main thread, waiting on a manager signal and calling the registered fucntion. * Main thread, waiting on a manager signal and calling the registered function.
*/ */
void ThreadFunc(); void ThreadFunc();

View file

@ -74,7 +74,7 @@ void Manager::BufferReleaseAndRegister() {
u32 Manager::GetAudioOutDeviceNames( u32 Manager::GetAudioOutDeviceNames(
std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const {
names.push_back({"DeviceOut"}); names.emplace_back("DeviceOut");
return 1; return 1;
} }

View file

@ -25,8 +25,8 @@ SystemManager& Manager::GetSystemManager() {
return *system_manager; return *system_manager;
} }
auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) Result Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params,
-> Result { u64& out_count) const {
if (!CheckValidRevision(params.revision)) { if (!CheckValidRevision(params.revision)) {
return Service::Audio::ERR_INVALID_REVISION; return Service::Audio::ERR_INVALID_REVISION;
} }
@ -54,7 +54,7 @@ void Manager::ReleaseSessionId(const s32 session_id) {
session_ids[--session_count] = session_id; session_ids[--session_count] = session_id;
} }
u32 Manager::GetSessionCount() { u32 Manager::GetSessionCount() const {
std::scoped_lock l{session_lock}; std::scoped_lock l{session_lock};
return session_count; return session_count;
} }

View file

@ -46,7 +46,7 @@ public:
* @param out_count - Output size of the required workbuffer. * @param out_count - Output size of the required workbuffer.
* @return Result code. * @return Result code.
*/ */
Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count); Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) const;
/** /**
* Get a new session id. * Get a new session id.
@ -60,14 +60,14 @@ public:
* *
* @return The number of active sessions. * @return The number of active sessions.
*/ */
u32 GetSessionCount(); u32 GetSessionCount() const;
/** /**
* Add a renderer system to the manager. * Add a renderer system to the manager.
* The system will be reguarly called to generate commands for the AudioRenderer. * The system will be regularly called to generate commands for the AudioRenderer.
* *
* @param system - The system to add. * @param system - The system to add.
* @return True if the system was sucessfully added, otherwise false. * @return True if the system was successfully added, otherwise false.
*/ */
bool AddSystem(System& system); bool AddSystem(System& system);
@ -75,7 +75,7 @@ public:
* Remove a renderer system from the manager. * Remove a renderer system from the manager.
* *
* @param system - The system to remove. * @param system - The system to remove.
* @return True if the system was sucessfully removed, otherwise false. * @return True if the system was successfully removed, otherwise false.
*/ */
bool RemoveSystem(System& system); bool RemoveSystem(System& system);
@ -94,7 +94,7 @@ private:
/// Number of active renderers /// Number of active renderers
u32 session_count{}; u32 session_count{};
/// Lock for interacting with the sessions /// Lock for interacting with the sessions
std::mutex session_lock{}; mutable std::mutex session_lock{};
/// Regularly generates commands from the registered systems for the AudioRenderer /// Regularly generates commands from the registered systems for the AudioRenderer
std::unique_ptr<SystemManager> system_manager{}; std::unique_ptr<SystemManager> system_manager{};
}; };

View file

@ -8,6 +8,10 @@
namespace AudioCore { namespace AudioCore {
struct AudioBuffer { struct AudioBuffer {
/// Timestamp this buffer started playing.
u64 start_timestamp;
/// Timestamp this buffer should finish playing.
u64 end_timestamp;
/// Timestamp this buffer completed playing. /// Timestamp this buffer completed playing.
s64 played_timestamp; s64 played_timestamp;
/// Game memory address for these samples. /// Game memory address for these samples.

View file

@ -36,7 +36,7 @@ public:
* *
* @param buffer - The new buffer. * @param buffer - The new buffer.
*/ */
void AppendBuffer(AudioBuffer& buffer) { void AppendBuffer(const AudioBuffer& buffer) {
std::scoped_lock l{lock}; std::scoped_lock l{lock};
buffers[appended_index] = buffer; buffers[appended_index] = buffer;
appended_count++; appended_count++;
@ -58,6 +58,7 @@ public:
if (index < 0) { if (index < 0) {
index += N; index += N;
} }
out_buffers.push_back(buffers[index]); out_buffers.push_back(buffers[index]);
registered_count++; registered_count++;
registered_index = (registered_index + 1) % append_limit; registered_index = (registered_index + 1) % append_limit;
@ -87,10 +88,12 @@ public:
/** /**
* Release all registered buffers. * Release all registered buffers.
* *
* @param timestamp - The released timestamp for this buffer. * @param core_timing - The CoreTiming instance
* @param session - The device session
*
* @return Is the buffer was released. * @return Is the buffer was released.
*/ */
bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session) {
std::scoped_lock l{lock}; std::scoped_lock l{lock};
bool buffer_released{false}; bool buffer_released{false};
while (registered_count > 0) { while (registered_count > 0) {
@ -100,7 +103,7 @@ public:
} }
// Check with the backend if this buffer can be released yet. // Check with the backend if this buffer can be released yet.
if (!session.IsBufferConsumed(buffers[index].tag)) { if (!session.IsBufferConsumed(buffers[index])) {
break; break;
} }
@ -280,6 +283,16 @@ public:
return true; return true;
} }
u64 GetNextTimestamp() const {
// Iterate backwards through the buffer queue, and take the most recent buffer's end
std::scoped_lock l{lock};
auto index{appended_index - 1};
if (index < 0) {
index += append_limit;
}
return buffers[index].end_timestamp;
}
private: private:
/// Buffer lock /// Buffer lock
mutable std::recursive_mutex lock{}; mutable std::recursive_mutex lock{};

View file

@ -7,11 +7,20 @@
#include "audio_core/device/device_session.h" #include "audio_core/device/device_session.h"
#include "audio_core/sink/sink_stream.h" #include "audio_core/sink/sink_stream.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h"
#include "core/memory.h" #include "core/memory.h"
namespace AudioCore { namespace AudioCore {
DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} using namespace std::literals;
constexpr auto INCREMENT_TIME{5ms};
DeviceSession::DeviceSession(Core::System& system_)
: system{system_}, thread_event{Core::Timing::CreateEvent(
"AudioOutSampleTick",
[this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
return ThreadFunc();
})} {}
DeviceSession::~DeviceSession() { DeviceSession::~DeviceSession() {
Finalize(); Finalize();
@ -50,25 +59,26 @@ void DeviceSession::Finalize() {
} }
void DeviceSession::Start() { void DeviceSession::Start() {
stream->SetPlayedSampleCount(played_sample_count); if (stream) {
stream->Start(); stream->Start();
system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME,
thread_event);
}
} }
void DeviceSession::Stop() { void DeviceSession::Stop() {
if (stream) { if (stream) {
played_sample_count = stream->GetPlayedSampleCount();
stream->Stop(); stream->Stop();
system.CoreTiming().UnscheduleEvent(thread_event, {});
} }
} }
void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
auto& memory{system.Memory()}; for (const auto& buffer : buffers) {
for (size_t i = 0; i < buffers.size(); i++) {
Sink::SinkBuffer new_buffer{ Sink::SinkBuffer new_buffer{
.frames = buffers[i].size / (channel_count * sizeof(s16)), .frames = buffer.size / (channel_count * sizeof(s16)),
.frames_played = 0, .frames_played = 0,
.tag = buffers[i].tag, .tag = buffer.tag,
.consumed = false, .consumed = false,
}; };
@ -76,26 +86,22 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
std::vector<s16> samples{}; std::vector<s16> samples{};
stream->AppendBuffer(new_buffer, samples); stream->AppendBuffer(new_buffer, samples);
} else { } else {
std::vector<s16> samples(buffers[i].size / sizeof(s16)); std::vector<s16> samples(buffer.size / sizeof(s16));
memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); system.Memory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size);
stream->AppendBuffer(new_buffer, samples); stream->AppendBuffer(new_buffer, samples);
} }
} }
} }
void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
if (type == Sink::StreamType::In) { if (type == Sink::StreamType::In) {
auto& memory{system.Memory()};
auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
} }
} }
bool DeviceSession::IsBufferConsumed(u64 tag) const { bool DeviceSession::IsBufferConsumed(const AudioBuffer& buffer) const {
if (stream) { return played_sample_count >= buffer.end_timestamp;
return stream->IsBufferConsumed(tag);
}
return true;
} }
void DeviceSession::SetVolume(f32 volume) const { void DeviceSession::SetVolume(f32 volume) const {
@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const {
} }
u64 DeviceSession::GetPlayedSampleCount() const { u64 DeviceSession::GetPlayedSampleCount() const {
if (stream) { return played_sample_count;
return stream->GetPlayedSampleCount(); }
std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
// Add 5ms of samples at a 48K sample rate.
played_sample_count += 48'000 * INCREMENT_TIME / 1s;
if (type == Sink::StreamType::Out) {
system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
} else {
system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true);
} }
return 0; return std::nullopt;
}
void DeviceSession::SetRingSize(u32 ring_size) {
stream->SetRingSize(ring_size);
} }
} // namespace AudioCore } // namespace AudioCore

View file

@ -3,6 +3,9 @@
#pragma once #pragma once
#include <chrono>
#include <memory>
#include <optional>
#include <span> #include <span>
#include "audio_core/common/common.h" #include "audio_core/common/common.h"
@ -11,9 +14,13 @@
namespace Core { namespace Core {
class System; class System;
} namespace Timing {
struct EventType;
} // namespace Timing
} // namespace Core
namespace AudioCore { namespace AudioCore {
namespace Sink { namespace Sink {
class SinkStream; class SinkStream;
struct SinkBuffer; struct SinkBuffer;
@ -55,22 +62,23 @@ public:
* *
* @param buffers - The buffers to play. * @param buffers - The buffers to play.
*/ */
void AppendBuffers(std::span<AudioBuffer> buffers) const; void AppendBuffers(std::span<const AudioBuffer> buffers) const;
/** /**
* (Audio In only) Pop samples from the backend, and write them back to this buffer's address. * (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
* *
* @param buffer - The buffer to write to. * @param buffer - The buffer to write to.
*/ */
void ReleaseBuffer(AudioBuffer& buffer) const; void ReleaseBuffer(const AudioBuffer& buffer) const;
/** /**
* Check if the buffer for the given tag has been consumed by the backend. * Check if the buffer for the given tag has been consumed by the backend.
* *
* @param tag - Unqiue tag of the buffer to check. * @param buffer - the buffer to check.
*
* @return true if the buffer has been consumed, otherwise false. * @return true if the buffer has been consumed, otherwise false.
*/ */
bool IsBufferConsumed(u64 tag) const; bool IsBufferConsumed(const AudioBuffer& buffer) const;
/** /**
* Start this device session, starting the backend stream. * Start this device session, starting the backend stream.
@ -96,6 +104,16 @@ public:
*/ */
u64 GetPlayedSampleCount() const; u64 GetPlayedSampleCount() const;
/*
* CoreTiming callback to increment played_sample_count over time.
*/
std::optional<std::chrono::nanoseconds> ThreadFunc();
/*
* Set the size of the ring buffer.
*/
void SetRingSize(u32 ring_size);
private: private:
/// System /// System
Core::System& system; Core::System& system;
@ -118,9 +136,13 @@ private:
/// Applet resource user id of this device session /// Applet resource user id of this device session
u64 applet_resource_user_id{}; u64 applet_resource_user_id{};
/// Total number of samples played by this device session /// Total number of samples played by this device session
u64 played_sample_count{}; std::atomic<u64> played_sample_count{};
/// Event increasing the played sample count every 5ms
std::shared_ptr<Core::Timing::EventType> thread_event;
/// Is this session initialised? /// Is this session initialised?
bool initialized{}; bool initialized{};
/// Buffer queue
std::vector<AudioBuffer> buffer_queue{};
}; };
} // namespace AudioCore } // namespace AudioCore

View file

@ -72,7 +72,7 @@ Kernel::KReadableEvent& In::GetBufferEvent() {
return event->GetReadableEvent(); return event->GetReadableEvent();
} }
f32 In::GetVolume() { f32 In::GetVolume() const {
std::scoped_lock l{parent_mutex}; std::scoped_lock l{parent_mutex};
return system.GetVolume(); return system.GetVolume();
} }
@ -82,17 +82,17 @@ void In::SetVolume(f32 volume) {
system.SetVolume(volume); system.SetVolume(volume);
} }
bool In::ContainsAudioBuffer(u64 tag) { bool In::ContainsAudioBuffer(u64 tag) const {
std::scoped_lock l{parent_mutex}; std::scoped_lock l{parent_mutex};
return system.ContainsAudioBuffer(tag); return system.ContainsAudioBuffer(tag);
} }
u32 In::GetBufferCount() { u32 In::GetBufferCount() const {
std::scoped_lock l{parent_mutex}; std::scoped_lock l{parent_mutex};
return system.GetBufferCount(); return system.GetBufferCount();
} }
u64 In::GetPlayedSampleCount() { u64 In::GetPlayedSampleCount() const {
std::scoped_lock l{parent_mutex}; std::scoped_lock l{parent_mutex};
return system.GetPlayedSampleCount(); return system.GetPlayedSampleCount();
} }

View file

@ -102,7 +102,7 @@ public:
* *
* @return The current volume. * @return The current volume.
*/ */
f32 GetVolume(); f32 GetVolume() const;
/** /**
* Set the system volume. * Set the system volume.
@ -117,21 +117,21 @@ public:
* @param tag - The tag to search for. * @param tag - The tag to search for.
* @return True if the buffer is in the system, otherwise false. * @return True if the buffer is in the system, otherwise false.
*/ */
bool ContainsAudioBuffer(u64 tag); bool ContainsAudioBuffer(u64 tag) const;
/** /**
* Get the maximum number of buffers. * Get the maximum number of buffers.
* *
* @return The maximum number of buffers. * @return The maximum number of buffers.
*/ */
u32 GetBufferCount(); u32 GetBufferCount() const;
/** /**
* Get the total played sample count for this audio in. * Get the total played sample count for this audio in.
* *
* @return The played sample count. * @return The played sample count.
*/ */
u64 GetPlayedSampleCount(); u64 GetPlayedSampleCount() const;
private: private:
/// The AudioIn::Manager this audio in is registered with /// The AudioIn::Manager this audio in is registered with

View file

@ -34,16 +34,16 @@ size_t System::GetSessionId() const {
return session_id; return session_id;
} }
std::string_view System::GetDefaultDeviceName() { std::string_view System::GetDefaultDeviceName() const {
return "BuiltInHeadset"; return "BuiltInHeadset";
} }
std::string_view System::GetDefaultUacDeviceName() { std::string_view System::GetDefaultUacDeviceName() const {
return "Uac"; return "Uac";
} }
Result System::IsConfigValid(const std::string_view device_name, Result System::IsConfigValid(const std::string_view device_name,
const AudioInParameter& in_params) { const AudioInParameter& in_params) const {
if ((device_name.size() > 0) && if ((device_name.size() > 0) &&
(device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) { (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) {
return Service::Audio::ERR_INVALID_DEVICE_NAME; return Service::Audio::ERR_INVALID_DEVICE_NAME;
@ -93,6 +93,7 @@ Result System::Start() {
std::vector<AudioBuffer> buffers_to_flush{}; std::vector<AudioBuffer> buffers_to_flush{};
buffers.RegisterBuffers(buffers_to_flush); buffers.RegisterBuffers(buffers_to_flush);
session->AppendBuffers(buffers_to_flush); session->AppendBuffers(buffers_to_flush);
session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
return ResultSuccess; return ResultSuccess;
} }
@ -112,8 +113,15 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
return false; return false;
} }
AudioBuffer new_buffer{ const auto timestamp{buffers.GetNextTimestamp()};
.played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; const AudioBuffer new_buffer{
.start_timestamp = timestamp,
.end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
.played_timestamp = 0,
.samples = buffer.samples,
.tag = tag,
.size = buffer.size,
};
buffers.AppendBuffer(new_buffer); buffers.AppendBuffer(new_buffer);
RegisterBuffers(); RegisterBuffers();
@ -194,11 +202,11 @@ void System::SetVolume(const f32 volume_) {
session->SetVolume(volume_); session->SetVolume(volume_);
} }
bool System::ContainsAudioBuffer(const u64 tag) { bool System::ContainsAudioBuffer(const u64 tag) const {
return buffers.ContainsBuffer(tag); return buffers.ContainsBuffer(tag);
} }
u32 System::GetBufferCount() { u32 System::GetBufferCount() const {
return buffers.GetAppendedRegisteredCount(); return buffers.GetAppendedRegisteredCount();
} }

View file

@ -68,7 +68,7 @@ public:
* *
* @return The default audio input device name. * @return The default audio input device name.
*/ */
std::string_view GetDefaultDeviceName(); std::string_view GetDefaultDeviceName() const;
/** /**
* Get the default USB audio input device name. * Get the default USB audio input device name.
@ -77,7 +77,7 @@ public:
* *
* @return The default USB audio input device name. * @return The default USB audio input device name.
*/ */
std::string_view GetDefaultUacDeviceName(); std::string_view GetDefaultUacDeviceName() const;
/** /**
* Is the given initialize config valid? * Is the given initialize config valid?
@ -86,7 +86,7 @@ public:
* @param in_params - Input parameters, see AudioInParameter. * @param in_params - Input parameters, see AudioInParameter.
* @return Result code. * @return Result code.
*/ */
Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params); Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params) const;
/** /**
* Initialize this system. * Initialize this system.
@ -208,7 +208,7 @@ public:
/** /**
* Set this system's current volume. * Set this system's current volume.
* *
* @param The new volume. * @param volume The new volume.
*/ */
void SetVolume(f32 volume); void SetVolume(f32 volume);
@ -218,14 +218,14 @@ public:
* @param tag - Unique tag to search for. * @param tag - Unique tag to search for.
* @return True if the buffer is in the system, otherwise false. * @return True if the buffer is in the system, otherwise false.
*/ */
bool ContainsAudioBuffer(u64 tag); bool ContainsAudioBuffer(u64 tag) const;
/** /**
* Get the maximum number of usable buffers (default 32). * Get the maximum number of usable buffers (default 32).
* *
* @return The number of buffers. * @return The number of buffers.
*/ */
u32 GetBufferCount(); u32 GetBufferCount() const;
/** /**
* Get the total number of samples played by this system. * Get the total number of samples played by this system.

View file

@ -72,7 +72,7 @@ Kernel::KReadableEvent& Out::GetBufferEvent() {
return event->GetReadableEvent(); return event->GetReadableEvent();
} }
f32 Out::GetVolume() { f32 Out::GetVolume() const {
std::scoped_lock l{parent_mutex}; std::scoped_lock l{parent_mutex};
return system.GetVolume(); return system.GetVolume();
} }
@ -82,17 +82,17 @@ void Out::SetVolume(const f32 volume) {
system.SetVolume(volume); system.SetVolume(volume);
} }
bool Out::ContainsAudioBuffer(const u64 tag) { bool Out::ContainsAudioBuffer(const u64 tag) const {
std::scoped_lock l{parent_mutex}; std::scoped_lock l{parent_mutex};
return system.ContainsAudioBuffer(tag); return system.ContainsAudioBuffer(tag);
} }
u32 Out::GetBufferCount() { u32 Out::GetBufferCount() const {
std::scoped_lock l{parent_mutex}; std::scoped_lock l{parent_mutex};
return system.GetBufferCount(); return system.GetBufferCount();
} }
u64 Out::GetPlayedSampleCount() { u64 Out::GetPlayedSampleCount() const {
std::scoped_lock l{parent_mutex}; std::scoped_lock l{parent_mutex};
return system.GetPlayedSampleCount(); return system.GetPlayedSampleCount();
} }

View file

@ -102,7 +102,7 @@ public:
* *
* @return The current volume. * @return The current volume.
*/ */
f32 GetVolume(); f32 GetVolume() const;
/** /**
* Set the system volume. * Set the system volume.
@ -117,21 +117,21 @@ public:
* @param tag - The tag to search for. * @param tag - The tag to search for.
* @return True if the buffer is in the system, otherwise false. * @return True if the buffer is in the system, otherwise false.
*/ */
bool ContainsAudioBuffer(u64 tag); bool ContainsAudioBuffer(u64 tag) const;
/** /**
* Get the maximum number of buffers. * Get the maximum number of buffers.
* *
* @return The maximum number of buffers. * @return The maximum number of buffers.
*/ */
u32 GetBufferCount(); u32 GetBufferCount() const;
/** /**
* Get the total played sample count for this audio out. * Get the total played sample count for this audio out.
* *
* @return The played sample count. * @return The played sample count.
*/ */
u64 GetPlayedSampleCount(); u64 GetPlayedSampleCount() const;
private: private:
/// The AudioOut::Manager this audio out is registered with /// The AudioOut::Manager this audio out is registered with

View file

@ -27,11 +27,12 @@ void System::Finalize() {
buffer_event->GetWritableEvent().Signal(); buffer_event->GetWritableEvent().Signal();
} }
std::string_view System::GetDefaultOutputDeviceName() { std::string_view System::GetDefaultOutputDeviceName() const {
return "DeviceOut"; return "DeviceOut";
} }
Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) { Result System::IsConfigValid(std::string_view device_name,
const AudioOutParameter& in_params) const {
if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) { if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) {
return Service::Audio::ERR_INVALID_DEVICE_NAME; return Service::Audio::ERR_INVALID_DEVICE_NAME;
} }
@ -92,6 +93,7 @@ Result System::Start() {
std::vector<AudioBuffer> buffers_to_flush{}; std::vector<AudioBuffer> buffers_to_flush{};
buffers.RegisterBuffers(buffers_to_flush); buffers.RegisterBuffers(buffers_to_flush);
session->AppendBuffers(buffers_to_flush); session->AppendBuffers(buffers_to_flush);
session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
return ResultSuccess; return ResultSuccess;
} }
@ -111,8 +113,15 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
return false; return false;
} }
AudioBuffer new_buffer{ const auto timestamp{buffers.GetNextTimestamp()};
.played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; const AudioBuffer new_buffer{
.start_timestamp = timestamp,
.end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
.played_timestamp = 0,
.samples = buffer.samples,
.tag = tag,
.size = buffer.size,
};
buffers.AppendBuffer(new_buffer); buffers.AppendBuffer(new_buffer);
RegisterBuffers(); RegisterBuffers();
@ -192,11 +201,11 @@ void System::SetVolume(const f32 volume_) {
session->SetVolume(volume_); session->SetVolume(volume_);
} }
bool System::ContainsAudioBuffer(const u64 tag) { bool System::ContainsAudioBuffer(const u64 tag) const {
return buffers.ContainsBuffer(tag); return buffers.ContainsBuffer(tag);
} }
u32 System::GetBufferCount() { u32 System::GetBufferCount() const {
return buffers.GetAppendedRegisteredCount(); return buffers.GetAppendedRegisteredCount();
} }

View file

@ -68,7 +68,7 @@ public:
* *
* @return The default audio output device name. * @return The default audio output device name.
*/ */
std::string_view GetDefaultOutputDeviceName(); std::string_view GetDefaultOutputDeviceName() const;
/** /**
* Is the given initialize config valid? * Is the given initialize config valid?
@ -77,7 +77,7 @@ public:
* @param in_params - Input parameters, see AudioOutParameter. * @param in_params - Input parameters, see AudioOutParameter.
* @return Result code. * @return Result code.
*/ */
Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params); Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) const;
/** /**
* Initialize this system. * Initialize this system.
@ -199,7 +199,7 @@ public:
/** /**
* Set this system's current volume. * Set this system's current volume.
* *
* @param The new volume. * @param volume The new volume.
*/ */
void SetVolume(f32 volume); void SetVolume(f32 volume);
@ -209,14 +209,14 @@ public:
* @param tag - Unique tag to search for. * @param tag - Unique tag to search for.
* @return True if the buffer is in the system, otherwise false. * @return True if the buffer is in the system, otherwise false.
*/ */
bool ContainsAudioBuffer(u64 tag); bool ContainsAudioBuffer(u64 tag) const;
/** /**
* Get the maximum number of usable buffers (default 32). * Get the maximum number of usable buffers (default 32).
* *
* @return The number of buffers. * @return The number of buffers.
*/ */
u32 GetBufferCount(); u32 GetBufferCount() const;
/** /**
* Get the total number of samples played by this system. * Get the total number of samples played by this system.

View file

@ -50,7 +50,7 @@ u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
return render_mailbox.GetRemainCommandCount(session_id); return render_mailbox.GetRemainCommandCount(session_id);
} }
void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) { void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) {
render_mailbox.SetCommandBuffer(session_id, command_buffer); render_mailbox.SetCommandBuffer(session_id, command_buffer);
} }

View file

@ -63,8 +63,6 @@ public:
/** /**
* Stop the ADSP. * Stop the ADSP.
*
* @return True if started or already running, otherwise false.
*/ */
void Stop(); void Stop();
@ -133,7 +131,7 @@ public:
* @param session_id - The session id to check (0 or 1). * @param session_id - The session id to check (0 or 1).
* @param command_buffer - The command buffer to process. * @param command_buffer - The command buffer to process.
*/ */
void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer); void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer);
/** /**
* Clear the command buffers (does not clear the time taken or the remaining command count) * Clear the command buffers (does not clear the time taken or the remaining command count)

View file

@ -51,7 +51,7 @@ CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) {
return command_buffers[session_id]; return command_buffers[session_id];
} }
void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) { void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
command_buffers[session_id] = buffer; command_buffers[session_id] = buffer;
} }
@ -106,9 +106,6 @@ void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
mailbox = mailbox_; mailbox = mailbox_;
thread = std::thread(&AudioRenderer::ThreadFunc, this); thread = std::thread(&AudioRenderer::ThreadFunc, this);
for (auto& stream : streams) {
stream->Start();
}
running = true; running = true;
} }
@ -130,6 +127,7 @@ void AudioRenderer::CreateSinkStreams() {
std::string name{fmt::format("ADSP_RenderStream-{}", i)}; std::string name{fmt::format("ADSP_RenderStream-{}", i)};
streams[i] = streams[i] =
sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
streams[i]->SetRingSize(4);
} }
} }
@ -198,11 +196,6 @@ void AudioRenderer::ThreadFunc() {
command_list_processor.Process(index) - start_time; command_list_processor.Process(index) - start_time;
} }
if (index == 0) {
auto stream{command_list_processor.GetOutputSinkStream()};
system.AudioCore().SetStreamQueue(stream->GetQueueSize());
}
const auto end_time{system.CoreTiming().GetClockTicks()}; const auto end_time{system.CoreTiming().GetClockTicks()};
command_buffer.remaining_command_count = command_buffer.remaining_command_count =

View file

@ -52,7 +52,7 @@ public:
/** /**
* Send a message from the host to the AudioRenderer. * Send a message from the host to the AudioRenderer.
* *
* @param message_ - The message to send to the AudioRenderer. * @param message - The message to send to the AudioRenderer.
*/ */
void HostSendMessage(RenderMessage message); void HostSendMessage(RenderMessage message);
@ -66,7 +66,7 @@ public:
/** /**
* Send a message from the AudioRenderer to the host. * Send a message from the AudioRenderer to the host.
* *
* @param message_ - The message to send to the host. * @param message - The message to send to the host.
*/ */
void ADSPSendMessage(RenderMessage message); void ADSPSendMessage(RenderMessage message);
@ -91,7 +91,7 @@ public:
* @param session_id - The session id to get (0 or 1). * @param session_id - The session id to get (0 or 1).
* @param buffer - The command buffer to set. * @param buffer - The command buffer to set.
*/ */
void SetCommandBuffer(u32 session_id, CommandBuffer& buffer); void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer);
/** /**
* Get the total render time taken for the last command lists sent. * Get the total render time taken for the last command lists sent.
@ -163,7 +163,7 @@ public:
/** /**
* Start the AudioRenderer. * Start the AudioRenderer.
* *
* @param The mailbox to use for this session. * @param mailbox The mailbox to use for this session.
*/ */
void Start(AudioRenderer_Mailbox* mailbox); void Start(AudioRenderer_Mailbox* mailbox);

View file

@ -33,10 +33,10 @@ public:
/** /**
* Initialize the processor. * Initialize the processor.
* *
* @param system_ - The core system. * @param system - The core system.
* @param buffer - The command buffer to process. * @param buffer - The command buffer to process.
* @param size - The size of the buffer. * @param size - The size of the buffer.
* @param stream_ - The stream to be used for sending the samples. * @param stream - The stream to be used for sending the samples.
*/ */
void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
@ -72,7 +72,8 @@ public:
/** /**
* Process the command list. * Process the command list.
* *
* @param index - Index of the current command list. * @param session_id - Session ID for the commands being processed.
*
* @return The time taken to process. * @return The time taken to process.
*/ */
u64 Process(u32 session_id); u64 Process(u32 session_id);
@ -89,7 +90,7 @@ public:
u8* commands{}; u8* commands{};
/// The command buffer size /// The command buffer size
u64 commands_buffer_size{}; u64 commands_buffer_size{};
/// The maximum processing time alloted /// The maximum processing time allotted
u64 max_process_time{}; u64 max_process_time{};
/// The number of commands in the buffer /// The number of commands in the buffer
u32 command_count{}; u32 command_count{};

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <span>
#include "audio_core/audio_core.h" #include "audio_core/audio_core.h"
#include "audio_core/common/feature_support.h" #include "audio_core/common/feature_support.h"
#include "audio_core/renderer/audio_device.h" #include "audio_core/renderer/audio_device.h"
@ -9,14 +12,33 @@
namespace AudioCore::AudioRenderer { namespace AudioCore::AudioRenderer {
constexpr std::array usb_device_names{
AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
AudioDevice::AudioDeviceName{"AudioTvOutput"},
AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"},
};
constexpr std::array device_names{
AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
AudioDevice::AudioDeviceName{"AudioTvOutput"},
};
constexpr std::array output_device_names{
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
AudioDevice::AudioDeviceName{"AudioTvOutput"},
AudioDevice::AudioDeviceName{"AudioExternalOutput"},
};
AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
const u32 revision) const u32 revision)
: output_sink{system.AudioCore().GetOutputSink()}, : output_sink{system.AudioCore().GetOutputSink()},
applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
const size_t max_count) { const size_t max_count) const {
std::span<AudioDeviceName> names{}; std::span<const AudioDeviceName> names{};
if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
names = usb_device_names; names = usb_device_names;
@ -24,7 +46,7 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
names = device_names; names = device_names;
} }
u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; const u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
for (u32 i = 0; i < out_count; i++) { for (u32 i = 0; i < out_count; i++) {
out_buffer.push_back(names[i]); out_buffer.push_back(names[i]);
} }
@ -32,8 +54,8 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
} }
u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
const size_t max_count) { const size_t max_count) const {
u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; const u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
for (u32 i = 0; i < out_count; i++) { for (u32 i = 0; i < out_count; i++) {
out_buffer.push_back(output_device_names[i]); out_buffer.push_back(output_device_names[i]);
@ -45,7 +67,7 @@ void AudioDevice::SetDeviceVolumes(const f32 volume) {
output_sink.SetDeviceVolume(volume); output_sink.SetDeviceVolume(volume);
} }
f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
return output_sink.GetDeviceVolume(); return output_sink.GetDeviceVolume();
} }

View file

@ -3,7 +3,7 @@
#pragma once #pragma once
#include <span> #include <string_view>
#include "audio_core/audio_render_manager.h" #include "audio_core/audio_render_manager.h"
@ -23,21 +23,13 @@ namespace AudioRenderer {
class AudioDevice { class AudioDevice {
public: public:
struct AudioDeviceName { struct AudioDeviceName {
std::array<char, 0x100> name; std::array<char, 0x100> name{};
AudioDeviceName(const char* name_) { constexpr AudioDeviceName(std::string_view name_) {
std::strncpy(name.data(), name_, name.size()); name_.copy(name.data(), name.size() - 1);
} }
}; };
std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput",
"AudioBuiltInSpeakerOutput", "AudioTvOutput",
"AudioUsbDeviceOutput"};
std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput",
"AudioBuiltInSpeakerOutput", "AudioTvOutput"};
std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput",
"AudioExternalOutput"};
explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
/** /**
@ -47,7 +39,7 @@ public:
* @param max_count - Maximum number of devices to write (count of out_buffer). * @param max_count - Maximum number of devices to write (count of out_buffer).
* @return Number of device names written. * @return Number of device names written.
*/ */
u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
/** /**
* Get a list of the available output devices. * Get a list of the available output devices.
@ -57,7 +49,7 @@ public:
* @param max_count - Maximum number of devices to write (count of out_buffer). * @param max_count - Maximum number of devices to write (count of out_buffer).
* @return Number of device names written. * @return Number of device names written.
*/ */
u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
/** /**
* Set the volume of all streams in the backend sink. * Set the volume of all streams in the backend sink.
@ -73,7 +65,7 @@ public:
* @param name - Name of the device to check. Unused. * @param name - Name of the device to check. Unused.
* @return Volume of the device. * @return Volume of the device.
*/ */
f32 GetDeviceVolume(std::string_view name); f32 GetDeviceVolume(std::string_view name) const;
private: private:
/// Backend output sink for the device /// Backend output sink for the device

View file

@ -34,7 +34,7 @@ void BehaviorInfo::ClearError() {
error_count = 0; error_count = 0;
} }
void BehaviorInfo::AppendError(ErrorInfo& error) { void BehaviorInfo::AppendError(const ErrorInfo& error) {
LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}", LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
error.error_code.raw, error.address); error.error_code.raw, error.address);
if (error_count < MaxErrors) { if (error_count < MaxErrors) {
@ -42,14 +42,16 @@ void BehaviorInfo::AppendError(ErrorInfo& error) {
} }
} }
void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const {
auto error_count_{std::min(error_count, MaxErrors)}; out_count = std::min(error_count, MaxErrors);
std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo));
for (size_t i = 0; i < error_count_; i++) { for (size_t i = 0; i < MaxErrors; i++) {
out_errors[i] = errors[i]; if (i < out_count) {
out_errors[i] = errors[i];
} else {
out_errors[i] = {};
}
} }
out_count = error_count_;
} }
void BehaviorInfo::UpdateFlags(const Flags flags_) { void BehaviorInfo::UpdateFlags(const Flags flags_) {

View file

@ -94,7 +94,7 @@ public:
* *
* @param error - The new error. * @param error - The new error.
*/ */
void AppendError(ErrorInfo& error); void AppendError(const ErrorInfo& error);
/** /**
* Copy errors to the given output container. * Copy errors to the given output container.
@ -102,7 +102,7 @@ public:
* @param out_errors - Output container to receive the errors. * @param out_errors - Output container to receive the errors.
* @param out_count - The number of errors written. * @param out_count - The number of errors written.
*/ */
void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count); void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const;
/** /**
* Update the behaviour flags. * Update the behaviour flags.

View file

@ -485,7 +485,7 @@ Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) {
return ResultSuccess; return ResultSuccess;
} }
Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) { Result InfoUpdater::UpdateErrorInfo(const BehaviorInfo& behaviour_) {
auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)}; auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)};
behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count); behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count);

View file

@ -130,7 +130,7 @@ public:
* @param behaviour - Behaviour to update. * @param behaviour - Behaviour to update.
* @return Result code. * @return Result code.
*/ */
Result UpdateErrorInfo(BehaviorInfo& behaviour); Result UpdateErrorInfo(const BehaviorInfo& behaviour);
/** /**
* Update splitter. * Update splitter.

View file

@ -191,6 +191,7 @@ public:
* @param volume - Current mix volume used for calculating the ramp. * @param volume - Current mix volume used for calculating the ramp.
* @param prev_volume - Previous mix volume, used for calculating the ramp, * @param prev_volume - Previous mix volume, used for calculating the ramp,
* also applied to the input. * also applied to the input.
* @param prev_samples - Previous sample buffer. Used for depopping.
* @param precision - Number of decimal bits for fixed point operations. * @param precision - Number of decimal bits for fixed point operations.
*/ */
void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
@ -208,6 +209,7 @@ public:
* @param volumes - Current mix volumes used for calculating the ramp. * @param volumes - Current mix volumes used for calculating the ramp.
* @param prev_volumes - Previous mix volumes, used for calculating the ramp, * @param prev_volumes - Previous mix volumes, used for calculating the ramp,
* also applied to the input. * also applied to the input.
* @param prev_samples - Previous sample buffer. Used for depopping.
* @param precision - Number of decimal bits for fixed point operations. * @param precision - Number of decimal bits for fixed point operations.
*/ */
void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
@ -297,11 +299,11 @@ public:
/** /**
* Generate a device sink command, adding it to the command list. * Generate a device sink command, adding it to the command list.
* *
* @param node_id - Node id of the voice this command is generated for. * @param node_id - Node id of the voice this command is generated for.
* @param buffer_offset - Base mix buffer offset to use. * @param buffer_offset - Base mix buffer offset to use.
* @param sink_info - The sink_info to generate this command from. * @param sink_info - The sink_info to generate this command from.
* @session_id - System session id this command is generated from. * @param session_id - System session id this command is generated from.
* @samples_buffer - The buffer to be sent to the sink if upsampling is not used. * @param samples_buffer - The buffer to be sent to the sink if upsampling is not used.
*/ */
void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
u32 session_id, std::span<s32> samples_buffer); u32 session_id, std::span<s32> samples_buffer);

View file

@ -197,9 +197,9 @@ public:
/** /**
* Generate an I3DL2 reverb effect command. * Generate an I3DL2 reverb effect command.
* *
* @param buffer_offset - Base mix buffer offset to use. * @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - I3DL2Reverb effect info. * @param effect_info - I3DL2Reverb effect info.
* @param node_id - Node id of the mix this command is generated for. * @param node_id - Node id of the mix this command is generated for.
*/ */
void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
s32 node_id); s32 node_id);
@ -207,18 +207,18 @@ public:
/** /**
* Generate an aux effect command. * Generate an aux effect command.
* *
* @param buffer_offset - Base mix buffer offset to use. * @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Aux effect info. * @param effect_info - Aux effect info.
* @param node_id - Node id of the mix this command is generated for. * @param node_id - Node id of the mix this command is generated for.
*/ */
void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
/** /**
* Generate a biquad filter effect command. * Generate a biquad filter effect command.
* *
* @param buffer_offset - Base mix buffer offset to use. * @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Aux effect info. * @param effect_info - Aux effect info.
* @param node_id - Node id of the mix this command is generated for. * @param node_id - Node id of the mix this command is generated for.
*/ */
void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
s32 node_id); s32 node_id);
@ -226,10 +226,10 @@ public:
/** /**
* Generate a light limiter effect command. * Generate a light limiter effect command.
* *
* @param buffer_offset - Base mix buffer offset to use. * @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Limiter effect info. * @param effect_info - Limiter effect info.
* @param node_id - Node id of the mix this command is generated for. * @param node_id - Node id of the mix this command is generated for.
* @param effect_index - Index for the statistics state. * @param effect_index - Index for the statistics state.
*/ */
void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
s32 node_id, u32 effect_index); s32 node_id, u32 effect_index);
@ -238,21 +238,20 @@ public:
* Generate a capture effect command. * Generate a capture effect command.
* Writes a mix buffer back to game memory. * Writes a mix buffer back to game memory.
* *
* @param buffer_offset - Base mix buffer offset to use. * @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Capture effect info. * @param effect_info - Capture effect info.
* @param node_id - Node id of the mix this command is generated for. * @param node_id - Node id of the mix this command is generated for.
*/ */
void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
/** /**
* Generate a compressor effect command. * Generate a compressor effect command.
* *
* @param buffer_offset - Base mix buffer offset to use. * @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Compressor effect info. * @param effect_info - Compressor effect info.
* @param node_id - Node id of the mix this command is generated for. * @param node_id - Node id of the mix this command is generated for.
*/ */
void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
const s32 node_id);
/** /**
* Generate all effect commands for a mix. * Generate all effect commands for a mix.
@ -318,8 +317,9 @@ public:
* Generate a performance command. * Generate a performance command.
* Used to report performance metrics of the AudioRenderer back to the game. * Used to report performance metrics of the AudioRenderer back to the game.
* *
* @param buffer_offset - Base mix buffer offset to use. * @param node_id - Node ID of the mix this command is generated for
* @param sink_info - Sink info to generate the commands from. * @param state - Output state of the generated performance command
* @param entry_addresses - Addresses to be written
*/ */
void GeneratePerformanceCommand(s32 node_id, PerformanceState state, void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
const PerformanceEntryAddresses& entry_addresses); const PerformanceEntryAddresses& entry_addresses);

View file

@ -11,7 +11,7 @@
namespace AudioCore::AudioRenderer { namespace AudioCore::AudioRenderer {
static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params,
CompressorInfo::State& state) { CompressorInfo::State& state) {
const auto ratio{1.0f / params.compressor_ratio}; const auto ratio{1.0f / params.compressor_ratio};
auto makeup_gain{0.0f}; auto makeup_gain{0.0f};
@ -31,9 +31,9 @@ static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& para
state.unk_20 = c; state.unk_20 = c;
} }
static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params,
CompressorInfo::State& state) { CompressorInfo::State& state) {
std::memset(&state, 0, sizeof(CompressorInfo::State)); state = {};
state.unk_00 = 0; state.unk_00 = 0;
state.unk_04 = 1.0f; state.unk_04 = 1.0f;
@ -42,7 +42,7 @@ static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params
SetCompressorEffectParameter(params, state); SetCompressorEffectParameter(params, state);
} }
static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
CompressorInfo::State& state, bool enabled, CompressorInfo::State& state, bool enabled,
std::vector<std::span<const s32>> input_buffers, std::vector<std::span<const s32>> input_buffers,
std::vector<std::span<s32>> output_buffers, u32 sample_count) { std::vector<std::span<s32>> output_buffers, u32 sample_count) {
@ -103,8 +103,7 @@ static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params,
} else { } else {
for (s16 channel = 0; channel < params.channel_count; channel++) { for (s16 channel = 0; channel < params.channel_count; channel++) {
if (params.inputs[channel] != params.outputs[channel]) { if (params.inputs[channel] != params.outputs[channel]) {
std::memcpy((char*)output_buffers[channel].data(), std::memcpy(output_buffers[channel].data(), input_buffers[channel].data(),
(char*)input_buffers[channel].data(),
output_buffers[channel].size_bytes()); output_buffers[channel].size_bytes());
} }
} }

View file

@ -7,17 +7,7 @@
#include "common/logging/log.h" #include "common/logging/log.h"
namespace AudioCore::AudioRenderer { namespace AudioCore::AudioRenderer {
/**
* Mix input mix buffer into output mix buffer, with volume applied to the input.
*
* @tparam Q - Number of bits for fixed point operations.
* @param output - Output mix buffer.
* @param input - Input mix buffer.
* @param volume - Volume applied to the input.
* @param ramp - Ramp applied to volume every sample.
* @param sample_count - Number of samples to process.
* @return The final gained input sample, used for depopping.
*/
template <size_t Q> template <size_t Q>
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
const f32 ramp_, const u32 sample_count) { const f32 ramp_, const u32 sample_count) {
@ -40,10 +30,8 @@ s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 vo
return sample.to_int(); return sample.to_int();
} }
template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32, template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32);
const u32); template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32);
template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32,
const u32);
void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};

View file

@ -61,13 +61,13 @@ struct MixRampCommand : ICommand {
* @tparam Q - Number of bits for fixed point operations. * @tparam Q - Number of bits for fixed point operations.
* @param output - Output mix buffer. * @param output - Output mix buffer.
* @param input - Input mix buffer. * @param input - Input mix buffer.
* @param volume - Volume applied to the input. * @param volume_ - Volume applied to the input.
* @param ramp - Ramp applied to volume every sample. * @param ramp_ - Ramp applied to volume every sample.
* @param sample_count - Number of samples to process. * @param sample_count - Number of samples to process.
* @return The final gained input sample, used for depopping. * @return The final gained input sample, used for depopping.
*/ */
template <size_t Q> template <size_t Q>
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_,
const f32 ramp_, const u32 sample_count); u32 sample_count);
} // namespace AudioCore::AudioRenderer } // namespace AudioCore::AudioRenderer

View file

@ -50,9 +50,9 @@ struct MixRampGroupedCommand : ICommand {
std::array<s16, MaxMixBuffers> inputs; std::array<s16, MaxMixBuffers> inputs;
/// Output mix buffer indexes for each mix buffer /// Output mix buffer indexes for each mix buffer
std::array<s16, MaxMixBuffers> outputs; std::array<s16, MaxMixBuffers> outputs;
/// Previous mix vloumes for each mix buffer /// Previous mix volumes for each mix buffer
std::array<f32, MaxMixBuffers> prev_volumes; std::array<f32, MaxMixBuffers> prev_volumes;
/// Current mix vloumes for each mix buffer /// Current mix volumes for each mix buffer
std::array<f32, MaxMixBuffers> volumes; std::array<f32, MaxMixBuffers> volumes;
/// Pointer to the previous sample buffer, used for depop /// Pointer to the previous sample buffer, used for depop
CpuAddr previous_samples; CpuAddr previous_samples;

View file

@ -46,6 +46,10 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
out_buffer.tag = reinterpret_cast<u64>(samples.data()); out_buffer.tag = reinterpret_cast<u64>(samples.data());
stream->AppendBuffer(out_buffer, samples); stream->AppendBuffer(out_buffer, samples);
if (stream->IsPaused()) {
stream->Start();
}
} }
bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {

View file

@ -15,15 +15,15 @@ class EffectContext {
public: public:
/** /**
* Initialize the effect context * Initialize the effect context
* @param effect_infos List of effect infos for this context * @param effect_infos_ - List of effect infos for this context
* @param effect_count The number of effects in the list * @param effect_count_ - The number of effects in the list
* @param result_states_cpu The workbuffer of result states for the CPU for this context * @param result_states_cpu_ - The workbuffer of result states for the CPU for this context
* @param result_states_dsp The workbuffer of result states for the DSP for this context * @param result_states_dsp_ - The workbuffer of result states for the DSP for this context
* @param state_count The number of result states * @param dsp_state_count - The number of result states
*/ */
void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, void Initialize(std::span<EffectInfoBase> effect_infos_, u32 effect_count_,
std::span<EffectResultState> result_states_cpu_, std::span<EffectResultState> result_states_cpu_,
std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count); std::span<EffectResultState> result_states_dsp_, size_t dsp_state_count);
/** /**
* Get the EffectInfo for a given index * Get the EffectInfo for a given index

View file

@ -291,7 +291,7 @@ public:
* Update the info with new parameters, version 1. * Update the info with new parameters, version 1.
* *
* @param error_info - Used to write call result code. * @param error_info - Used to write call result code.
* @param in_params - New parameters to update the info with. * @param params - New parameters to update the info with.
* @param pool_mapper - Pool for mapping buffers. * @param pool_mapper - Pool for mapping buffers.
*/ */
virtual void Update(BehaviorInfo::ErrorInfo& error_info, virtual void Update(BehaviorInfo::ErrorInfo& error_info,
@ -305,7 +305,7 @@ public:
* Update the info with new parameters, version 2. * Update the info with new parameters, version 2.
* *
* @param error_info - Used to write call result code. * @param error_info - Used to write call result code.
* @param in_params - New parameters to update the info with. * @param params - New parameters to update the info with.
* @param pool_mapper - Pool for mapping buffers. * @param pool_mapper - Pool for mapping buffers.
*/ */
virtual void Update(BehaviorInfo::ErrorInfo& error_info, virtual void Update(BehaviorInfo::ErrorInfo& error_info,

View file

@ -99,7 +99,7 @@ public:
return out_sample; return out_sample;
} }
Common::FixedPoint<50, 14> Read() { Common::FixedPoint<50, 14> Read() const {
return *output; return *output;
} }
@ -110,7 +110,7 @@ public:
} }
} }
Common::FixedPoint<50, 14> TapOut(const s32 index) { Common::FixedPoint<50, 14> TapOut(const s32 index) const {
auto out{input - (index + 1)}; auto out{input - (index + 1)};
if (out < buffer.data()) { if (out < buffer.data()) {
out += max_delay + 1; out += max_delay + 1;

View file

@ -95,7 +95,7 @@ public:
return out_sample; return out_sample;
} }
Common::FixedPoint<50, 14> Read() { Common::FixedPoint<50, 14> Read() const {
return *output; return *output;
} }
@ -106,7 +106,7 @@ public:
} }
} }
Common::FixedPoint<50, 14> TapOut(const s32 index) { Common::FixedPoint<50, 14> TapOut(const s32 index) const {
auto out{input - (index + 1)}; auto out{input - (index + 1)};
if (out < buffer.data()) { if (out < buffer.data()) {
out += sample_count; out += sample_count;

View file

@ -19,8 +19,8 @@ public:
/** /**
* Setup a new AddressInfo. * Setup a new AddressInfo.
* *
* @param cpu_address - The CPU address of this region. * @param cpu_address_ - The CPU address of this region.
* @param size - The size of this region. * @param size_ - The size of this region.
*/ */
void Setup(CpuAddr cpu_address_, u64 size_) { void Setup(CpuAddr cpu_address_, u64 size_) {
cpu_address = cpu_address_; cpu_address = cpu_address_;
@ -42,7 +42,6 @@ public:
* Assign this region to a memory pool. * Assign this region to a memory pool.
* *
* @param memory_pool_ - Memory pool to assign. * @param memory_pool_ - Memory pool to assign.
* @return The CpuAddr address of this region.
*/ */
void SetPool(MemoryPoolInfo* memory_pool_) { void SetPool(MemoryPoolInfo* memory_pool_) {
memory_pool = memory_pool_; memory_pool = memory_pool_;

View file

@ -56,7 +56,7 @@ class NodeStates {
* *
* @return The current stack position. * @return The current stack position.
*/ */
u32 Count() { u32 Count() const {
return pos; return pos;
} }
@ -83,7 +83,7 @@ class NodeStates {
* *
* @return The node on the top of the stack. * @return The node on the top of the stack.
*/ */
u32 top() { u32 top() const {
return stack[pos - 1]; return stack[pos - 1];
} }
@ -112,11 +112,11 @@ public:
/** /**
* Initialize the node states. * Initialize the node states.
* *
* @param buffer - The workbuffer to use. Unused. * @param buffer_ - The workbuffer to use. Unused.
* @param node_buffer_size - The size of the workbuffer. Unused. * @param node_buffer_size - The size of the workbuffer. Unused.
* @param count - The number of nodes in the graph. * @param count - The number of nodes in the graph.
*/ */
void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count); void Initialize(std::span<u8> buffer_, u64 node_buffer_size, u32 count);
/** /**
* Sort the graph. Only calls DepthFirstSearch. * Sort the graph. Only calls DepthFirstSearch.

View file

@ -73,7 +73,8 @@ public:
* Calculate the required size for the performance workbuffer. * Calculate the required size for the performance workbuffer.
* *
* @param behavior - Check which version is supported. * @param behavior - Check which version is supported.
* @param params - Input parameters. * @param params - Input parameters.
*
* @return Required workbuffer size. * @return Required workbuffer size.
*/ */
static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame( static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame(
@ -104,7 +105,7 @@ public:
* @param workbuffer - Workbuffer to use for performance frames. * @param workbuffer - Workbuffer to use for performance frames.
* @param workbuffer_size - Size of the workbuffer. * @param workbuffer_size - Size of the workbuffer.
* @param params - Input parameters. * @param params - Input parameters.
* @param behavior - Behaviour to check version and data format. * @param behavior - Behaviour to check version and data format.
* @param memory_pool - Used to translate the workbuffer address for the DSP. * @param memory_pool - Used to translate the workbuffer address for the DSP.
*/ */
virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size, virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
@ -160,7 +161,8 @@ public:
* workbuffer, to be written by the AudioRenderer. * workbuffer, to be written by the AudioRenderer.
* *
* @param addresses - Filled with pointers to the new detail, which should be passed * @param addresses - Filled with pointers to the new detail, which should be passed
* to the AudioRenderer with Performance commands to be written. * to the AudioRenderer with Performance commands to be written.
* @param detail_type - Performance detail type.
* @param entry_type - The type of this detail. See PerformanceEntryType * @param entry_type - The type of this detail. See PerformanceEntryType
* @param node_id - Node id for this detail. * @param node_id - Node id for this detail.
* @return True if a new detail was created and the offsets are valid, otherwise false. * @return True if a new detail was created and the offsets are valid, otherwise false.

View file

@ -15,17 +15,14 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
MP_RGB(60, 19, 97)); MP_RGB(60, 19, 97));
namespace AudioCore::AudioRenderer { namespace AudioCore::AudioRenderer {
constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL};
constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL};
SystemManager::SystemManager(Core::System& core_) SystemManager::SystemManager(Core::System& core_)
: core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
thread_event{Core::Timing::CreateEvent( thread_event{Core::Timing::CreateEvent(
"AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
return ThreadFunc2(time); return ThreadFunc2(time);
})} { })} {}
core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); });
}
SystemManager::~SystemManager() { SystemManager::~SystemManager() {
Stop(); Stop();
@ -36,8 +33,8 @@ bool SystemManager::InitializeUnsafe() {
if (adsp.Start()) { if (adsp.Start()) {
active = true; active = true;
thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), RENDER_TIME,
BaseRenderTime - RenderTimeOffset, thread_event); thread_event);
} }
} }
@ -121,42 +118,9 @@ void SystemManager::ThreadFunc() {
} }
std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt};
const auto queue_size{core.AudioCore().GetStreamQueue()};
switch (state) {
case StreamState::Filling:
if (queue_size >= 5) {
new_schedule_time = BaseRenderTime;
state = StreamState::Steady;
}
break;
case StreamState::Steady:
if (queue_size <= 2) {
new_schedule_time = BaseRenderTime - RenderTimeOffset;
state = StreamState::Filling;
} else if (queue_size > 5) {
new_schedule_time = BaseRenderTime + RenderTimeOffset;
state = StreamState::Draining;
}
break;
case StreamState::Draining:
if (queue_size <= 5) {
new_schedule_time = BaseRenderTime;
state = StreamState::Steady;
}
break;
}
update.store(true); update.store(true);
update.notify_all(); update.notify_all();
return new_schedule_time; return std::nullopt;
}
void SystemManager::PauseCallback(bool paused) {
if (paused && core.IsPoweredOn() && core.IsShuttingDown()) {
update.store(true);
update.notify_all();
}
} }
} // namespace AudioCore::AudioRenderer } // namespace AudioCore::AudioRenderer

View file

@ -73,13 +73,6 @@ private:
*/ */
std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time); std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
/**
* Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc.
*
* @param paused - Are we pausing or resuming?
*/
void PauseCallback(bool paused);
enum class StreamState { enum class StreamState {
Filling, Filling,
Steady, Steady,
@ -106,8 +99,6 @@ private:
std::shared_ptr<Core::Timing::EventType> thread_event; std::shared_ptr<Core::Timing::EventType> thread_event;
/// Atomic for main thread to wait on /// Atomic for main thread to wait on
std::atomic<bool> update{}; std::atomic<bool> update{};
/// Current state of the streams
StreamState state{StreamState::Filling};
}; };
} // namespace AudioCore::AudioRenderer } // namespace AudioCore::AudioRenderer

View file

@ -27,7 +27,7 @@ public:
/** /**
* Free the given upsampler. * Free the given upsampler.
* *
* @param The upsampler to be freed. * @param info The upsampler to be freed.
*/ */
void Free(UpsamplerInfo* info); void Free(UpsamplerInfo* info);

View file

@ -185,7 +185,8 @@ public:
/** /**
* Does this voice ned an update? * Does this voice ned an update?
* *
* @param params - Input parametetrs to check matching. * @param params - Input parameters to check matching.
*
* @return True if this voice needs an update, otherwise false. * @return True if this voice needs an update, otherwise false.
*/ */
bool ShouldUpdateParameters(const InParameter& params) const; bool ShouldUpdateParameters(const InParameter& params) const;
@ -194,9 +195,9 @@ public:
* Update the parameters of this voice. * Update the parameters of this voice.
* *
* @param error_info - Output error code. * @param error_info - Output error code.
* @param params - Input parametters to udpate from. * @param params - Input parameters to update from.
* @param pool_mapper - Used to map buffers. * @param pool_mapper - Used to map buffers.
* @param behavior - behavior to check supported features. * @param behavior - behavior to check supported features.
*/ */
void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
const PoolMapper& pool_mapper, const BehaviorInfo& behavior); const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
@ -218,12 +219,12 @@ public:
/** /**
* Update all wavebuffers. * Update all wavebuffers.
* *
* @param error_infos - Output 2D array of errors, 2 per wavebuffer. * @param error_infos - Output 2D array of errors, 2 per wavebuffer.
* @param error_count - Number of errors provided. Unused. * @param error_count - Number of errors provided. Unused.
* @param params - Input parametters to be used for the update. * @param params - Input parameters to be used for the update.
* @param voice_states - The voice states for each channel in this voice to be updated. * @param voice_states - The voice states for each channel in this voice to be updated.
* @param pool_mapper - Used to map the wavebuffers. * @param pool_mapper - Used to map the wavebuffers.
* @param behavior - Used to check for supported features. * @param behavior - Used to check for supported features.
*/ */
void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos, void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
u32 error_count, const InParameter& params, u32 error_count, const InParameter& params,
@ -233,13 +234,13 @@ public:
/** /**
* Update a wavebuffer. * Update a wavebuffer.
* *
* @param error_infos - Output array of errors. * @param error_info - Output array of errors.
* @param wave_buffer - The wavebuffer to be updated. * @param wave_buffer - The wavebuffer to be updated.
* @param wave_buffer_internal - Input parametters to be used for the update. * @param wave_buffer_internal - Input parametters to be used for the update.
* @param sample_format - Sample format of the wavebuffer. * @param sample_format - Sample format of the wavebuffer.
* @param valid - Is this wavebuffer valid? * @param valid - Is this wavebuffer valid?
* @param pool_mapper - Used to map the wavebuffers. * @param pool_mapper - Used to map the wavebuffers.
* @param behavior - Used to check for supported features. * @param behavior - Used to check for supported features.
*/ */
void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer, void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
const WaveBufferInternal& wave_buffer_internal, const WaveBufferInternal& wave_buffer_internal,
@ -276,7 +277,7 @@ public:
/** /**
* Check if this voice has any mixing connections. * Check if this voice has any mixing connections.
* *
* @return True if this voice participes in mixing, otherwise false. * @return True if this voice participates in mixing, otherwise false.
*/ */
bool HasAnyConnection() const; bool HasAnyConnection() const;
@ -301,7 +302,8 @@ public:
/** /**
* Update this voice on command generation. * Update this voice on command generation.
* *
* @param voice_states - Voice states for these wavebuffers. * @param voice_context - Voice context for these wavebuffers.
*
* @return True if this voice should be generated, otherwise false. * @return True if this voice should be generated, otherwise false.
*/ */
bool UpdateForCommandGeneration(VoiceContext& voice_context); bool UpdateForCommandGeneration(VoiceContext& voice_context);

View file

@ -1,21 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <atomic>
#include <span> #include <span>
#include <vector>
#include "audio_core/audio_core.h" #include "audio_core/common/common.h"
#include "audio_core/audio_event.h"
#include "audio_core/audio_manager.h"
#include "audio_core/sink/cubeb_sink.h" #include "audio_core/sink/cubeb_sink.h"
#include "audio_core/sink/sink_stream.h" #include "audio_core/sink/sink_stream.h"
#include "common/assert.h"
#include "common/fixed_point.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/reader_writer_queue.h"
#include "common/ring_buffer.h"
#include "common/settings.h"
#include "core/core.h" #include "core/core.h"
#ifdef _WIN32 #ifdef _WIN32
@ -42,10 +34,10 @@ public:
* @param system_ - Core system. * @param system_ - Core system.
* @param event - Event used only for audio renderer, signalled on buffer consume. * @param event - Event used only for audio renderer, signalled on buffer consume.
*/ */
CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_, CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_,
cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
const StreamType type_, Core::System& system_) StreamType type_, Core::System& system_)
: ctx{ctx_}, type{type_}, system{system_} { : SinkStream(system_, type_), ctx{ctx_} {
#ifdef _WIN32 #ifdef _WIN32
CoInitializeEx(nullptr, COINIT_MULTITHREADED); CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif #endif
@ -79,12 +71,10 @@ public:
minimum_latency = std::max(minimum_latency, 256u); minimum_latency = std::max(minimum_latency, 256u);
playing_buffer.consumed = true; LOG_INFO(Service_Audio,
"Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
LOG_DEBUG(Service_Audio, "latency {}",
"Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " name, type, params.rate, params.channels, system_channels, minimum_latency);
"latency {}",
name, type, params.rate, params.channels, system_channels, minimum_latency);
auto init_error{0}; auto init_error{0};
if (type == StreamType::In) { if (type == StreamType::In) {
@ -111,6 +101,8 @@ public:
~CubebSinkStream() override { ~CubebSinkStream() override {
LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
Unstall();
if (!ctx) { if (!ctx) {
return; return;
} }
@ -136,21 +128,14 @@ public:
* @param resume - Set to true if this is resuming the stream a previously-active stream. * @param resume - Set to true if this is resuming the stream a previously-active stream.
* Default false. * Default false.
*/ */
void Start(const bool resume = false) override { void Start(bool resume = false) override {
if (!ctx) { if (!ctx || !paused) {
return; return;
} }
if (resume && was_playing) { paused = false;
if (cubeb_stream_start(stream_backend) != CUBEB_OK) { if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
}
paused = false;
} else if (!resume) {
if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
}
paused = false;
} }
} }
@ -158,206 +143,19 @@ public:
* Stop the sink stream. * Stop the sink stream.
*/ */
void Stop() override { void Stop() override {
if (!ctx) { Unstall();
if (!ctx || paused) {
return; return;
} }
paused = true;
if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
} }
was_playing.store(!paused);
paused = true;
}
/**
* Append a new buffer and its samples to a waiting queue to play.
*
* @param buffer - Audio buffer information to be queued.
* @param samples - The s16 samples to be queue for playback.
*/
void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
if (type == StreamType::In) {
queue.enqueue(buffer);
queued_buffers++;
} else {
constexpr s32 min{std::numeric_limits<s16>::min()};
constexpr s32 max{std::numeric_limits<s16>::max()};
auto yuzu_volume{Settings::Volume()};
if (yuzu_volume > 1.0f) {
yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume);
}
auto volume{system_volume * device_volume * yuzu_volume};
if (system_channels == 6 && device_channels == 2) {
// We're given 6 channels, but our device only outputs 2, so downmix.
constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
for (u32 read_index = 0, write_index = 0; read_index < samples.size();
read_index += system_channels, write_index += device_channels) {
const auto left_sample{
((Common::FixedPoint<49, 15>(
samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
down_mix_coeff[0] +
samples[read_index + static_cast<u32>(Channels::Center)] *
down_mix_coeff[1] +
samples[read_index + static_cast<u32>(Channels::LFE)] *
down_mix_coeff[2] +
samples[read_index + static_cast<u32>(Channels::BackLeft)] *
down_mix_coeff[3]) *
volume)
.to_int()};
const auto right_sample{
((Common::FixedPoint<49, 15>(
samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
down_mix_coeff[0] +
samples[read_index + static_cast<u32>(Channels::Center)] *
down_mix_coeff[1] +
samples[read_index + static_cast<u32>(Channels::LFE)] *
down_mix_coeff[2] +
samples[read_index + static_cast<u32>(Channels::BackRight)] *
down_mix_coeff[3]) *
volume)
.to_int()};
samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
static_cast<s16>(std::clamp(left_sample, min, max));
samples[write_index + static_cast<u32>(Channels::FrontRight)] =
static_cast<s16>(std::clamp(right_sample, min, max));
}
samples.resize(samples.size() / system_channels * device_channels);
} else if (system_channels == 2 && device_channels == 6) {
// We need moar samples! Not all games will provide 6 channel audio.
// TODO: Implement some upmixing here. Currently just passthrough, with other
// channels left as silence.
std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
for (u32 read_index = 0, write_index = 0; read_index < samples.size();
read_index += system_channels, write_index += device_channels) {
const auto left_sample{static_cast<s16>(std::clamp(
static_cast<s32>(
static_cast<f32>(
samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
volume),
min, max))};
new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
const auto right_sample{static_cast<s16>(std::clamp(
static_cast<s32>(
static_cast<f32>(
samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
volume),
min, max))};
new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
right_sample;
}
samples = std::move(new_samples);
} else if (volume != 1.0f) {
for (u32 i = 0; i < samples.size(); i++) {
samples[i] = static_cast<s16>(std::clamp(
static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
}
}
samples_buffer.Push(samples);
queue.enqueue(buffer);
queued_buffers++;
}
}
/**
* Release a buffer. Audio In only, will fill a buffer with recorded samples.
*
* @param num_samples - Maximum number of samples to receive.
* @return Vector of recorded samples. May have fewer than num_samples.
*/
std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
static constexpr s32 min = std::numeric_limits<s16>::min();
static constexpr s32 max = std::numeric_limits<s16>::max();
auto samples{samples_buffer.Pop(num_samples)};
// TODO: Up-mix to 6 channels if the game expects it.
// For audio input this is unlikely to ever be the case though.
// Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
// TODO: Play with this and find something that works better.
auto volume{system_volume * device_volume * 8};
for (u32 i = 0; i < samples.size(); i++) {
samples[i] = static_cast<s16>(
std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
}
if (samples.size() < num_samples) {
samples.resize(num_samples, 0);
}
return samples;
}
/**
* Check if a certain buffer has been consumed (fully played).
*
* @param tag - Unique tag of a buffer to check for.
* @return True if the buffer has been played, otherwise false.
*/
bool IsBufferConsumed(const u64 tag) override {
if (released_buffer.tag == 0) {
if (!released_buffers.try_dequeue(released_buffer)) {
return false;
}
}
if (released_buffer.tag == tag) {
released_buffer.tag = 0;
return true;
}
return false;
}
/**
* Empty out the buffer queue.
*/
void ClearQueue() override {
samples_buffer.Pop();
while (queue.pop()) {
}
while (released_buffers.pop()) {
}
queued_buffers = 0;
released_buffer = {};
playing_buffer = {};
playing_buffer.consumed = true;
} }
private: private:
/**
* Signal events back to the audio system that a buffer was played/can be filled.
*
* @param buffer - Consumed audio buffer to be released.
*/
void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
auto& manager{system.AudioCore().GetAudioManager()};
switch (type) {
case StreamType::Out:
released_buffers.enqueue(buffer);
manager.SetEvent(Event::Type::AudioOutManager, true);
break;
case StreamType::In:
released_buffers.enqueue(buffer);
manager.SetEvent(Event::Type::AudioInManager, true);
break;
case StreamType::Render:
break;
}
}
/** /**
* Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
* provide samples to be copied (audio in). * provide samples to be copied (audio in).
@ -378,106 +176,15 @@ private:
const std::size_t num_channels = impl->GetDeviceChannels(); const std::size_t num_channels = impl->GetDeviceChannels();
const std::size_t frame_size = num_channels; const std::size_t frame_size = num_channels;
const std::size_t frame_size_bytes = frame_size * sizeof(s16);
const std::size_t num_frames{static_cast<size_t>(num_frames_)}; const std::size_t num_frames{static_cast<size_t>(num_frames_)};
size_t frames_written{0};
[[maybe_unused]] bool underrun{false};
if (impl->type == StreamType::In) { if (impl->type == StreamType::In) {
// INPUT
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff), std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
num_frames * frame_size}; num_frames * frame_size};
impl->ProcessAudioIn(input_buffer, num_frames);
while (frames_written < num_frames) {
auto& playing_buffer{impl->playing_buffer};
// If the playing buffer has been consumed or has no frames, we need a new one
if (playing_buffer.consumed || playing_buffer.frames == 0) {
if (!impl->queue.try_dequeue(impl->playing_buffer)) {
// If no buffer was available we've underrun, just push the samples and
// continue.
underrun = true;
impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
(num_frames - frames_written) * frame_size);
frames_written = num_frames;
continue;
} else {
// Successfully got a new buffer, mark the old one as consumed and signal.
impl->queued_buffers--;
impl->SignalEvent(impl->playing_buffer);
}
}
// Get the minimum frames available between the currently playing buffer, and the
// amount we have left to fill
size_t frames_available{
std::min(playing_buffer.frames - playing_buffer.frames_played,
num_frames - frames_written)};
impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
frames_available * frame_size);
frames_written += frames_available;
playing_buffer.frames_played += frames_available;
// If that's all the frames in the current buffer, add its samples and mark it as
// consumed
if (playing_buffer.frames_played >= playing_buffer.frames) {
impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
impl->playing_buffer.consumed = true;
}
}
std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
frame_size_bytes);
} else { } else {
// OUTPUT
std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size}; std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
while (frames_written < num_frames) {
auto& playing_buffer{impl->playing_buffer};
// If the playing buffer has been consumed or has no frames, we need a new one
if (playing_buffer.consumed || playing_buffer.frames == 0) {
if (!impl->queue.try_dequeue(impl->playing_buffer)) {
// If no buffer was available we've underrun, fill the remaining buffer with
// the last written frame and continue.
underrun = true;
for (size_t i = frames_written; i < num_frames; i++) {
std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
frame_size_bytes);
}
frames_written = num_frames;
continue;
} else {
// Successfully got a new buffer, mark the old one as consumed and signal.
impl->queued_buffers--;
impl->SignalEvent(impl->playing_buffer);
}
}
// Get the minimum frames available between the currently playing buffer, and the
// amount we have left to fill
size_t frames_available{
std::min(playing_buffer.frames - playing_buffer.frames_played,
num_frames - frames_written)};
impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
frames_available * frame_size);
frames_written += frames_available;
playing_buffer.frames_played += frames_available;
// If that's all the frames in the current buffer, add its samples and mark it as
// consumed
if (playing_buffer.frames_played >= playing_buffer.frames) {
impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
impl->playing_buffer.consumed = true;
}
}
std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
frame_size_bytes);
} }
return num_frames_; return num_frames_;
@ -490,32 +197,12 @@ private:
* @param user_data - Custom data pointer passed along, points to a CubebSinkStream. * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
* @param state - New state of the device. * @param state - New state of the device.
*/ */
static void StateCallback([[maybe_unused]] cubeb_stream* stream, static void StateCallback(cubeb_stream*, void*, cubeb_state) {}
[[maybe_unused]] void* user_data,
[[maybe_unused]] cubeb_state state) {}
/// Main Cubeb context /// Main Cubeb context
cubeb* ctx{}; cubeb* ctx{};
/// Cubeb stream backend /// Cubeb stream backend
cubeb_stream* stream_backend{}; cubeb_stream* stream_backend{};
/// Name of this stream
std::string name{};
/// Type of this stream
StreamType type;
/// Core system
Core::System& system;
/// Ring buffer of the samples waiting to be played or consumed
Common::RingBuffer<s16, 0x10000> samples_buffer;
/// Audio buffers queued and waiting to play
Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
/// The currently-playing audio buffer
::AudioCore::Sink::SinkBuffer playing_buffer{};
/// Audio buffers which have been played and are in queue to be released by the audio system
Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
/// Currently released buffer waiting to be taken by the audio system
::AudioCore::Sink::SinkBuffer released_buffer{};
/// The last played (or received) frame of audio, used when the callback underruns
std::array<s16, MaxChannels> last_frame{};
}; };
CubebSink::CubebSink(std::string_view target_device_name) { CubebSink::CubebSink(std::string_view target_device_name) {
@ -569,15 +256,15 @@ CubebSink::~CubebSink() {
#endif #endif
} }
SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels, SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels,
const std::string& name, const StreamType type) { const std::string& name, StreamType type) {
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
ctx, device_channels, system_channels, output_device, input_device, name, type, system)); ctx, device_channels, system_channels, output_device, input_device, name, type, system));
return stream.get(); return stream.get();
} }
void CubebSink::CloseStream(const SinkStream* stream) { void CubebSink::CloseStream(SinkStream* stream) {
for (size_t i = 0; i < sink_streams.size(); i++) { for (size_t i = 0; i < sink_streams.size(); i++) {
if (sink_streams[i].get() == stream) { if (sink_streams[i].get() == stream) {
sink_streams[i].reset(); sink_streams[i].reset();
@ -591,18 +278,6 @@ void CubebSink::CloseStreams() {
sink_streams.clear(); sink_streams.clear();
} }
void CubebSink::PauseStreams() {
for (auto& stream : sink_streams) {
stream->Stop();
}
}
void CubebSink::UnpauseStreams() {
for (auto& stream : sink_streams) {
stream->Start(true);
}
}
f32 CubebSink::GetDeviceVolume() const { f32 CubebSink::GetDeviceVolume() const {
if (sink_streams.empty()) { if (sink_streams.empty()) {
return 1.0f; return 1.0f;
@ -611,19 +286,19 @@ f32 CubebSink::GetDeviceVolume() const {
return sink_streams[0]->GetDeviceVolume(); return sink_streams[0]->GetDeviceVolume();
} }
void CubebSink::SetDeviceVolume(const f32 volume) { void CubebSink::SetDeviceVolume(f32 volume) {
for (auto& stream : sink_streams) { for (auto& stream : sink_streams) {
stream->SetDeviceVolume(volume); stream->SetDeviceVolume(volume);
} }
} }
void CubebSink::SetSystemVolume(const f32 volume) { void CubebSink::SetSystemVolume(f32 volume) {
for (auto& stream : sink_streams) { for (auto& stream : sink_streams) {
stream->SetSystemVolume(volume); stream->SetSystemVolume(volume);
} }
} }
std::vector<std::string> ListCubebSinkDevices(const bool capture) { std::vector<std::string> ListCubebSinkDevices(bool capture) {
std::vector<std::string> device_list; std::vector<std::string> device_list;
cubeb* ctx; cubeb* ctx;

View file

@ -34,8 +34,7 @@ public:
* May differ from the device's channel count. * May differ from the device's channel count.
* @param name - Name of this stream. * @param name - Name of this stream.
* @param type - Type of this stream, render/in/out. * @param type - Type of this stream, render/in/out.
* @param event - Audio render only, a signal used to prevent the renderer running too *
* fast.
* @return A pointer to the created SinkStream * @return A pointer to the created SinkStream
*/ */
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
@ -46,23 +45,13 @@ public:
* *
* @param stream - The stream to close. * @param stream - The stream to close.
*/ */
void CloseStream(const SinkStream* stream) override; void CloseStream(SinkStream* stream) override;
/** /**
* Close all streams. * Close all streams.
*/ */
void CloseStreams() override; void CloseStreams() override;
/**
* Pause all streams.
*/
void PauseStreams() override;
/**
* Unpause all streams.
*/
void UnpauseStreams() override;
/** /**
* Get the device volume. Set from calls to the IAudioDevice service. * Get the device volume. Set from calls to the IAudioDevice service.
* *
@ -101,7 +90,7 @@ private:
}; };
/** /**
* Get a list of conencted devices from Cubeb. * Get a list of connected devices from Cubeb.
* *
* @param capture - Return input (capture) devices if true, otherwise output devices. * @param capture - Return input (capture) devices if true, otherwise output devices.
*/ */

View file

@ -3,10 +3,29 @@
#pragma once #pragma once
#include <string>
#include <string_view>
#include <vector>
#include "audio_core/sink/sink.h" #include "audio_core/sink/sink.h"
#include "audio_core/sink/sink_stream.h" #include "audio_core/sink/sink_stream.h"
namespace Core {
class System;
} // namespace Core
namespace AudioCore::Sink { namespace AudioCore::Sink {
class NullSinkStreamImpl final : public SinkStream {
public:
explicit NullSinkStreamImpl(Core::System& system_, StreamType type_)
: SinkStream{system_, type_} {}
~NullSinkStreamImpl() override {}
void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {}
std::vector<s16> ReleaseBuffer(u64) override {
return {};
}
};
/** /**
* A no-op sink for when no audio out is wanted. * A no-op sink for when no audio out is wanted.
*/ */
@ -15,17 +34,16 @@ public:
explicit NullSink(std::string_view) {} explicit NullSink(std::string_view) {}
~NullSink() override = default; ~NullSink() override = default;
SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system, SinkStream* AcquireSinkStream(Core::System& system, u32, const std::string&,
[[maybe_unused]] u32 system_channels, StreamType type) override {
[[maybe_unused]] const std::string& name, if (null_sink == nullptr) {
[[maybe_unused]] StreamType type) override { null_sink = std::make_unique<NullSinkStreamImpl>(system, type);
return &null_sink_stream; }
return null_sink.get();
} }
void CloseStream([[maybe_unused]] const SinkStream* stream) override {} void CloseStream(SinkStream*) override {}
void CloseStreams() override {} void CloseStreams() override {}
void PauseStreams() override {}
void UnpauseStreams() override {}
f32 GetDeviceVolume() const override { f32 GetDeviceVolume() const override {
return 1.0f; return 1.0f;
} }
@ -33,20 +51,7 @@ public:
void SetSystemVolume(f32 volume) override {} void SetSystemVolume(f32 volume) override {}
private: private:
struct NullSinkStreamImpl final : SinkStream { SinkStreamPtr null_sink{};
void Finalize() override {}
void Start(bool resume = false) override {}
void Stop() override {}
void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer,
[[maybe_unused]] std::vector<s16>& samples) override {}
std::vector<s16> ReleaseBuffer([[maybe_unused]] u64 num_samples) override {
return {};
}
bool IsBufferConsumed([[maybe_unused]] const u64 tag) {
return true;
}
void ClearQueue() override {}
} null_sink_stream;
}; };
} // namespace AudioCore::Sink } // namespace AudioCore::Sink

View file

@ -1,20 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm> #include <span>
#include <atomic> #include <vector>
#include "audio_core/audio_core.h" #include "audio_core/common/common.h"
#include "audio_core/audio_event.h"
#include "audio_core/audio_manager.h"
#include "audio_core/sink/sdl2_sink.h" #include "audio_core/sink/sdl2_sink.h"
#include "audio_core/sink/sink_stream.h" #include "audio_core/sink/sink_stream.h"
#include "common/assert.h"
#include "common/fixed_point.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/reader_writer_queue.h"
#include "common/ring_buffer.h"
#include "common/settings.h"
#include "core/core.h" #include "core/core.h"
// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 // Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
@ -44,10 +37,9 @@ public:
* @param system_ - Core system. * @param system_ - Core system.
* @param event - Event used only for audio renderer, signalled on buffer consume. * @param event - Event used only for audio renderer, signalled on buffer consume.
*/ */
SDLSinkStream(u32 device_channels_, const u32 system_channels_, SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device,
const std::string& output_device, const std::string& input_device, const std::string& input_device, StreamType type_, Core::System& system_)
const StreamType type_, Core::System& system_) : SinkStream{system_, type_} {
: type{type_}, system{system_} {
system_channels = system_channels_; system_channels = system_channels_;
device_channels = device_channels_; device_channels = device_channels_;
@ -63,8 +55,6 @@ public:
spec.callback = &SDLSinkStream::DataCallback; spec.callback = &SDLSinkStream::DataCallback;
spec.userdata = this; spec.userdata = this;
playing_buffer.consumed = true;
std::string device_name{output_device}; std::string device_name{output_device};
bool capture{false}; bool capture{false};
if (type == StreamType::In) { if (type == StreamType::In) {
@ -84,31 +74,30 @@ public:
return; return;
} }
LOG_DEBUG(Service_Audio, LOG_INFO(Service_Audio,
"Opening sdl stream {} with: rate {} channels {} (system channels {}) " "Opening SDL stream {} with: rate {} channels {} (system channels {}) "
" samples {}", " samples {}",
device, obtained.freq, obtained.channels, system_channels, obtained.samples); device, obtained.freq, obtained.channels, system_channels, obtained.samples);
} }
/** /**
* Destroy the sink stream. * Destroy the sink stream.
*/ */
~SDLSinkStream() override { ~SDLSinkStream() override {
if (device == 0) { LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name);
return; Finalize();
}
SDL_CloseAudioDevice(device);
} }
/** /**
* Finalize the sink stream. * Finalize the sink stream.
*/ */
void Finalize() override { void Finalize() override {
Unstall();
if (device == 0) { if (device == 0) {
return; return;
} }
Stop();
SDL_CloseAudioDevice(device); SDL_CloseAudioDevice(device);
} }
@ -118,216 +107,28 @@ public:
* @param resume - Set to true if this is resuming the stream a previously-active stream. * @param resume - Set to true if this is resuming the stream a previously-active stream.
* Default false. * Default false.
*/ */
void Start(const bool resume = false) override { void Start(bool resume = false) override {
if (device == 0) { if (device == 0 || !paused) {
return; return;
} }
if (resume && was_playing) { paused = false;
SDL_PauseAudioDevice(device, 0); SDL_PauseAudioDevice(device, 0);
paused = false;
} else if (!resume) {
SDL_PauseAudioDevice(device, 0);
paused = false;
}
} }
/** /**
* Stop the sink stream. * Stop the sink stream.
*/ */
void Stop() { void Stop() override {
if (device == 0) { Unstall();
if (device == 0 || paused) {
return; return;
} }
SDL_PauseAudioDevice(device, 1);
paused = true; paused = true;
} SDL_PauseAudioDevice(device, 1);
/**
* Append a new buffer and its samples to a waiting queue to play.
*
* @param buffer - Audio buffer information to be queued.
* @param samples - The s16 samples to be queue for playback.
*/
void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
if (type == StreamType::In) {
queue.enqueue(buffer);
queued_buffers++;
} else {
constexpr s32 min = std::numeric_limits<s16>::min();
constexpr s32 max = std::numeric_limits<s16>::max();
auto yuzu_volume{Settings::Volume()};
auto volume{system_volume * device_volume * yuzu_volume};
if (system_channels == 6 && device_channels == 2) {
// We're given 6 channels, but our device only outputs 2, so downmix.
constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
for (u32 read_index = 0, write_index = 0; read_index < samples.size();
read_index += system_channels, write_index += device_channels) {
const auto left_sample{
((Common::FixedPoint<49, 15>(
samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
down_mix_coeff[0] +
samples[read_index + static_cast<u32>(Channels::Center)] *
down_mix_coeff[1] +
samples[read_index + static_cast<u32>(Channels::LFE)] *
down_mix_coeff[2] +
samples[read_index + static_cast<u32>(Channels::BackLeft)] *
down_mix_coeff[3]) *
volume)
.to_int()};
const auto right_sample{
((Common::FixedPoint<49, 15>(
samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
down_mix_coeff[0] +
samples[read_index + static_cast<u32>(Channels::Center)] *
down_mix_coeff[1] +
samples[read_index + static_cast<u32>(Channels::LFE)] *
down_mix_coeff[2] +
samples[read_index + static_cast<u32>(Channels::BackRight)] *
down_mix_coeff[3]) *
volume)
.to_int()};
samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
static_cast<s16>(std::clamp(left_sample, min, max));
samples[write_index + static_cast<u32>(Channels::FrontRight)] =
static_cast<s16>(std::clamp(right_sample, min, max));
}
samples.resize(samples.size() / system_channels * device_channels);
} else if (system_channels == 2 && device_channels == 6) {
// We need moar samples! Not all games will provide 6 channel audio.
// TODO: Implement some upmixing here. Currently just passthrough, with other
// channels left as silence.
std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
for (u32 read_index = 0, write_index = 0; read_index < samples.size();
read_index += system_channels, write_index += device_channels) {
const auto left_sample{static_cast<s16>(std::clamp(
static_cast<s32>(
static_cast<f32>(
samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
volume),
min, max))};
new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
const auto right_sample{static_cast<s16>(std::clamp(
static_cast<s32>(
static_cast<f32>(
samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
volume),
min, max))};
new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
right_sample;
}
samples = std::move(new_samples);
} else if (volume != 1.0f) {
for (u32 i = 0; i < samples.size(); i++) {
samples[i] = static_cast<s16>(std::clamp(
static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
}
}
samples_buffer.Push(samples);
queue.enqueue(buffer);
queued_buffers++;
}
}
/**
* Release a buffer. Audio In only, will fill a buffer with recorded samples.
*
* @param num_samples - Maximum number of samples to receive.
* @return Vector of recorded samples. May have fewer than num_samples.
*/
std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
static constexpr s32 min = std::numeric_limits<s16>::min();
static constexpr s32 max = std::numeric_limits<s16>::max();
auto samples{samples_buffer.Pop(num_samples)};
// TODO: Up-mix to 6 channels if the game expects it.
// For audio input this is unlikely to ever be the case though.
// Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
// TODO: Play with this and find something that works better.
auto volume{system_volume * device_volume * 8};
for (u32 i = 0; i < samples.size(); i++) {
samples[i] = static_cast<s16>(
std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
}
if (samples.size() < num_samples) {
samples.resize(num_samples, 0);
}
return samples;
}
/**
* Check if a certain buffer has been consumed (fully played).
*
* @param tag - Unique tag of a buffer to check for.
* @return True if the buffer has been played, otherwise false.
*/
bool IsBufferConsumed(const u64 tag) override {
if (released_buffer.tag == 0) {
if (!released_buffers.try_dequeue(released_buffer)) {
return false;
}
}
if (released_buffer.tag == tag) {
released_buffer.tag = 0;
return true;
}
return false;
}
/**
* Empty out the buffer queue.
*/
void ClearQueue() override {
samples_buffer.Pop();
while (queue.pop()) {
}
while (released_buffers.pop()) {
}
released_buffer = {};
playing_buffer = {};
playing_buffer.consumed = true;
queued_buffers = 0;
} }
private: private:
/**
* Signal events back to the audio system that a buffer was played/can be filled.
*
* @param buffer - Consumed audio buffer to be released.
*/
void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
auto& manager{system.AudioCore().GetAudioManager()};
switch (type) {
case StreamType::Out:
released_buffers.enqueue(buffer);
manager.SetEvent(Event::Type::AudioOutManager, true);
break;
case StreamType::In:
released_buffers.enqueue(buffer);
manager.SetEvent(Event::Type::AudioInManager, true);
break;
case StreamType::Render:
break;
}
}
/** /**
* Main callback from SDL. Either expects samples from us (audio render/audio out), or will * Main callback from SDL. Either expects samples from us (audio render/audio out), or will
* provide samples to be copied (audio in). * provide samples to be copied (audio in).
@ -345,122 +146,20 @@ private:
const std::size_t num_channels = impl->GetDeviceChannels(); const std::size_t num_channels = impl->GetDeviceChannels();
const std::size_t frame_size = num_channels; const std::size_t frame_size = num_channels;
const std::size_t frame_size_bytes = frame_size * sizeof(s16);
const std::size_t num_frames{len / num_channels / sizeof(s16)}; const std::size_t num_frames{len / num_channels / sizeof(s16)};
size_t frames_written{0};
[[maybe_unused]] bool underrun{false};
if (impl->type == StreamType::In) { if (impl->type == StreamType::In) {
std::span<s16> input_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
num_frames * frame_size};
while (frames_written < num_frames) { impl->ProcessAudioIn(input_buffer, num_frames);
auto& playing_buffer{impl->playing_buffer};
// If the playing buffer has been consumed or has no frames, we need a new one
if (playing_buffer.consumed || playing_buffer.frames == 0) {
if (!impl->queue.try_dequeue(impl->playing_buffer)) {
// If no buffer was available we've underrun, just push the samples and
// continue.
underrun = true;
impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
(num_frames - frames_written) * frame_size);
frames_written = num_frames;
continue;
} else {
impl->queued_buffers--;
impl->SignalEvent(impl->playing_buffer);
}
}
// Get the minimum frames available between the currently playing buffer, and the
// amount we have left to fill
size_t frames_available{
std::min(playing_buffer.frames - playing_buffer.frames_played,
num_frames - frames_written)};
impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
frames_available * frame_size);
frames_written += frames_available;
playing_buffer.frames_played += frames_available;
// If that's all the frames in the current buffer, add its samples and mark it as
// consumed
if (playing_buffer.frames_played >= playing_buffer.frames) {
impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
impl->playing_buffer.consumed = true;
}
}
std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
frame_size_bytes);
} else { } else {
std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
while (frames_written < num_frames) {
auto& playing_buffer{impl->playing_buffer};
// If the playing buffer has been consumed or has no frames, we need a new one
if (playing_buffer.consumed || playing_buffer.frames == 0) {
if (!impl->queue.try_dequeue(impl->playing_buffer)) {
// If no buffer was available we've underrun, fill the remaining buffer with
// the last written frame and continue.
underrun = true;
for (size_t i = frames_written; i < num_frames; i++) {
std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
frame_size_bytes);
}
frames_written = num_frames;
continue;
} else {
impl->queued_buffers--;
impl->SignalEvent(impl->playing_buffer);
}
}
// Get the minimum frames available between the currently playing buffer, and the
// amount we have left to fill
size_t frames_available{
std::min(playing_buffer.frames - playing_buffer.frames_played,
num_frames - frames_written)};
impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
frames_available * frame_size);
frames_written += frames_available;
playing_buffer.frames_played += frames_available;
// If that's all the frames in the current buffer, add its samples and mark it as
// consumed
if (playing_buffer.frames_played >= playing_buffer.frames) {
impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
impl->playing_buffer.consumed = true;
}
}
std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
frame_size_bytes);
} }
} }
/// SDL device id of the opened input/output device /// SDL device id of the opened input/output device
SDL_AudioDeviceID device{}; SDL_AudioDeviceID device{};
/// Type of this stream
StreamType type;
/// Core system
Core::System& system;
/// Ring buffer of the samples waiting to be played or consumed
Common::RingBuffer<s16, 0x10000> samples_buffer;
/// Audio buffers queued and waiting to play
Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
/// The currently-playing audio buffer
::AudioCore::Sink::SinkBuffer playing_buffer{};
/// Audio buffers which have been played and are in queue to be released by the audio system
Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
/// Currently released buffer waiting to be taken by the audio system
::AudioCore::Sink::SinkBuffer released_buffer{};
/// The last played (or received) frame of audio, used when the callback underruns
std::array<s16, MaxChannels> last_frame{};
}; };
SDLSink::SDLSink(std::string_view target_device_name) { SDLSink::SDLSink(std::string_view target_device_name) {
@ -482,14 +181,14 @@ SDLSink::SDLSink(std::string_view target_device_name) {
SDLSink::~SDLSink() = default; SDLSink::~SDLSink() = default;
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels,
const std::string&, const StreamType type) { const std::string&, StreamType type) {
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
device_channels, system_channels, output_device, input_device, type, system)); device_channels, system_channels, output_device, input_device, type, system));
return stream.get(); return stream.get();
} }
void SDLSink::CloseStream(const SinkStream* stream) { void SDLSink::CloseStream(SinkStream* stream) {
for (size_t i = 0; i < sink_streams.size(); i++) { for (size_t i = 0; i < sink_streams.size(); i++) {
if (sink_streams[i].get() == stream) { if (sink_streams[i].get() == stream) {
sink_streams[i].reset(); sink_streams[i].reset();
@ -503,18 +202,6 @@ void SDLSink::CloseStreams() {
sink_streams.clear(); sink_streams.clear();
} }
void SDLSink::PauseStreams() {
for (auto& stream : sink_streams) {
stream->Stop();
}
}
void SDLSink::UnpauseStreams() {
for (auto& stream : sink_streams) {
stream->Start();
}
}
f32 SDLSink::GetDeviceVolume() const { f32 SDLSink::GetDeviceVolume() const {
if (sink_streams.empty()) { if (sink_streams.empty()) {
return 1.0f; return 1.0f;
@ -523,19 +210,19 @@ f32 SDLSink::GetDeviceVolume() const {
return sink_streams[0]->GetDeviceVolume(); return sink_streams[0]->GetDeviceVolume();
} }
void SDLSink::SetDeviceVolume(const f32 volume) { void SDLSink::SetDeviceVolume(f32 volume) {
for (auto& stream : sink_streams) { for (auto& stream : sink_streams) {
stream->SetDeviceVolume(volume); stream->SetDeviceVolume(volume);
} }
} }
void SDLSink::SetSystemVolume(const f32 volume) { void SDLSink::SetSystemVolume(f32 volume) {
for (auto& stream : sink_streams) { for (auto& stream : sink_streams) {
stream->SetSystemVolume(volume); stream->SetSystemVolume(volume);
} }
} }
std::vector<std::string> ListSDLSinkDevices(const bool capture) { std::vector<std::string> ListSDLSinkDevices(bool capture) {
std::vector<std::string> device_list; std::vector<std::string> device_list;
if (!SDL_WasInit(SDL_INIT_AUDIO)) { if (!SDL_WasInit(SDL_INIT_AUDIO)) {

View file

@ -32,8 +32,7 @@ public:
* May differ from the device's channel count. * May differ from the device's channel count.
* @param name - Name of this stream. * @param name - Name of this stream.
* @param type - Type of this stream, render/in/out. * @param type - Type of this stream, render/in/out.
* @param event - Audio render only, a signal used to prevent the renderer running too *
* fast.
* @return A pointer to the created SinkStream * @return A pointer to the created SinkStream
*/ */
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
@ -44,23 +43,13 @@ public:
* *
* @param stream - The stream to close. * @param stream - The stream to close.
*/ */
void CloseStream(const SinkStream* stream) override; void CloseStream(SinkStream* stream) override;
/** /**
* Close all streams. * Close all streams.
*/ */
void CloseStreams() override; void CloseStreams() override;
/**
* Pause all streams.
*/
void PauseStreams() override;
/**
* Unpause all streams.
*/
void UnpauseStreams() override;
/** /**
* Get the device volume. Set from calls to the IAudioDevice service. * Get the device volume. Set from calls to the IAudioDevice service.
* *
@ -92,7 +81,7 @@ private:
}; };
/** /**
* Get a list of conencted devices from Cubeb. * Get a list of connected devices from SDL.
* *
* @param capture - Return input (capture) devices if true, otherwise output devices. * @param capture - Return input (capture) devices if true, otherwise output devices.
*/ */

View file

@ -32,23 +32,13 @@ public:
* *
* @param stream - The stream to close. * @param stream - The stream to close.
*/ */
virtual void CloseStream(const SinkStream* stream) = 0; virtual void CloseStream(SinkStream* stream) = 0;
/** /**
* Close all streams. * Close all streams.
*/ */
virtual void CloseStreams() = 0; virtual void CloseStreams() = 0;
/**
* Pause all streams.
*/
virtual void PauseStreams() = 0;
/**
* Unpause all streams.
*/
virtual void UnpauseStreams() = 0;
/** /**
* Create a new sink stream, kept within this sink, with a pointer returned for use. * Create a new sink stream, kept within this sink, with a pointer returned for use.
* Do not free the returned pointer. When done with the stream, call CloseStream on the sink. * Do not free the returned pointer. When done with the stream, call CloseStream on the sink.
@ -58,8 +48,7 @@ public:
* May differ from the device's channel count. * May differ from the device's channel count.
* @param name - Name of this stream. * @param name - Name of this stream.
* @param type - Type of this stream, render/in/out. * @param type - Type of this stream, render/in/out.
* @param event - Audio render only, a signal used to prevent the renderer running too *
* fast.
* @return A pointer to the created SinkStream * @return A pointer to the created SinkStream
*/ */
virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,

View file

@ -5,7 +5,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "audio_core/sink/null_sink.h"
#include "audio_core/sink/sink_details.h" #include "audio_core/sink/sink_details.h"
#ifdef HAVE_CUBEB #ifdef HAVE_CUBEB
#include "audio_core/sink/cubeb_sink.h" #include "audio_core/sink/cubeb_sink.h"
@ -13,6 +13,7 @@
#ifdef HAVE_SDL2 #ifdef HAVE_SDL2
#include "audio_core/sink/sdl2_sink.h" #include "audio_core/sink/sdl2_sink.h"
#endif #endif
#include "audio_core/sink/null_sink.h"
#include "common/logging/log.h" #include "common/logging/log.h"
namespace AudioCore::Sink { namespace AudioCore::Sink {
@ -59,8 +60,7 @@ const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) {
if (sink_id == "auto" || iter == std::end(sink_details)) { if (sink_id == "auto" || iter == std::end(sink_details)) {
if (sink_id != "auto") { if (sink_id != "auto") {
LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}", LOG_ERROR(Audio, "Invalid sink_id {}", sink_id);
sink_id);
} }
// Auto-select. // Auto-select.
// sink_details is ordered in terms of desirability, with the best choice at the front. // sink_details is ordered in terms of desirability, with the best choice at the front.

View file

@ -0,0 +1,279 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <atomic>
#include <memory>
#include <span>
#include <vector>
#include "audio_core/audio_core.h"
#include "audio_core/common/common.h"
#include "audio_core/sink/sink_stream.h"
#include "common/common_types.h"
#include "common/fixed_point.h"
#include "common/settings.h"
#include "core/core.h"
namespace AudioCore::Sink {
void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
if (type == StreamType::In) {
queue.enqueue(buffer);
queued_buffers++;
return;
}
constexpr s32 min{std::numeric_limits<s16>::min()};
constexpr s32 max{std::numeric_limits<s16>::max()};
auto yuzu_volume{Settings::Volume()};
if (yuzu_volume > 1.0f) {
yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume);
}
auto volume{system_volume * device_volume * yuzu_volume};
if (system_channels == 6 && device_channels == 2) {
// We're given 6 channels, but our device only outputs 2, so downmix.
constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
for (u32 read_index = 0, write_index = 0; read_index < samples.size();
read_index += system_channels, write_index += device_channels) {
const auto left_sample{
((Common::FixedPoint<49, 15>(
samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
down_mix_coeff[0] +
samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) *
volume)
.to_int()};
const auto right_sample{
((Common::FixedPoint<49, 15>(
samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
down_mix_coeff[0] +
samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) *
volume)
.to_int()};
samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
static_cast<s16>(std::clamp(left_sample, min, max));
samples[write_index + static_cast<u32>(Channels::FrontRight)] =
static_cast<s16>(std::clamp(right_sample, min, max));
}
samples.resize(samples.size() / system_channels * device_channels);
} else if (system_channels == 2 && device_channels == 6) {
// We need moar samples! Not all games will provide 6 channel audio.
// TODO: Implement some upmixing here. Currently just passthrough, with other
// channels left as silence.
std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
for (u32 read_index = 0, write_index = 0; read_index < samples.size();
read_index += system_channels, write_index += device_channels) {
const auto left_sample{static_cast<s16>(std::clamp(
static_cast<s32>(
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
volume),
min, max))};
new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
const auto right_sample{static_cast<s16>(std::clamp(
static_cast<s32>(
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
volume),
min, max))};
new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample;
}
samples = std::move(new_samples);
} else if (volume != 1.0f) {
for (u32 i = 0; i < samples.size(); i++) {
samples[i] = static_cast<s16>(
std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
}
}
samples_buffer.Push(samples);
queue.enqueue(buffer);
queued_buffers++;
}
std::vector<s16> SinkStream::ReleaseBuffer(u64 num_samples) {
constexpr s32 min = std::numeric_limits<s16>::min();
constexpr s32 max = std::numeric_limits<s16>::max();
auto samples{samples_buffer.Pop(num_samples)};
// TODO: Up-mix to 6 channels if the game expects it.
// For audio input this is unlikely to ever be the case though.
// Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
// TODO: Play with this and find something that works better.
auto volume{system_volume * device_volume * 8};
for (u32 i = 0; i < samples.size(); i++) {
samples[i] = static_cast<s16>(
std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
}
if (samples.size() < num_samples) {
samples.resize(num_samples, 0);
}
return samples;
}
void SinkStream::ClearQueue() {
samples_buffer.Pop();
while (queue.pop()) {
}
queued_buffers = 0;
playing_buffer = {};
playing_buffer.consumed = true;
}
void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames) {
const std::size_t num_channels = GetDeviceChannels();
const std::size_t frame_size = num_channels;
const std::size_t frame_size_bytes = frame_size * sizeof(s16);
size_t frames_written{0};
// If we're paused or going to shut down, we don't want to consume buffers as coretiming is
// paused and we'll desync, so just return.
if (system.IsPaused() || system.IsShuttingDown()) {
return;
}
if (queued_buffers > max_queue_size) {
Stall();
}
while (frames_written < num_frames) {
// If the playing buffer has been consumed or has no frames, we need a new one
if (playing_buffer.consumed || playing_buffer.frames == 0) {
if (!queue.try_dequeue(playing_buffer)) {
// If no buffer was available we've underrun, just push the samples and
// continue.
samples_buffer.Push(&input_buffer[frames_written * frame_size],
(num_frames - frames_written) * frame_size);
frames_written = num_frames;
continue;
}
// Successfully dequeued a new buffer.
queued_buffers--;
}
// Get the minimum frames available between the currently playing buffer, and the
// amount we have left to fill
size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
num_frames - frames_written)};
samples_buffer.Push(&input_buffer[frames_written * frame_size],
frames_available * frame_size);
frames_written += frames_available;
playing_buffer.frames_played += frames_available;
// If that's all the frames in the current buffer, add its samples and mark it as
// consumed
if (playing_buffer.frames_played >= playing_buffer.frames) {
playing_buffer.consumed = true;
}
}
std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes);
if (queued_buffers <= max_queue_size) {
Unstall();
}
}
void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames) {
const std::size_t num_channels = GetDeviceChannels();
const std::size_t frame_size = num_channels;
const std::size_t frame_size_bytes = frame_size * sizeof(s16);
size_t frames_written{0};
// If we're paused or going to shut down, we don't want to consume buffers as coretiming is
// paused and we'll desync, so just play silence.
if (system.IsPaused() || system.IsShuttingDown()) {
constexpr std::array<s16, 6> silence{};
for (size_t i = frames_written; i < num_frames; i++) {
std::memcpy(&output_buffer[i * frame_size], &silence[0], frame_size_bytes);
}
return;
}
// Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get
// queued up (30+) but not all at once, which causes constant stalling here, so just let the
// video play out without attempting to stall.
// Can hopefully remove this later with a more complete NVDEC implementation.
const auto nvdec_active{system.AudioCore().IsNVDECActive()};
if (!nvdec_active && queued_buffers > max_queue_size) {
Stall();
}
while (frames_written < num_frames) {
// If the playing buffer has been consumed or has no frames, we need a new one
if (playing_buffer.consumed || playing_buffer.frames == 0) {
if (!queue.try_dequeue(playing_buffer)) {
// If no buffer was available we've underrun, fill the remaining buffer with
// the last written frame and continue.
for (size_t i = frames_written; i < num_frames; i++) {
std::memcpy(&output_buffer[i * frame_size], &last_frame[0], frame_size_bytes);
}
frames_written = num_frames;
continue;
}
// Successfully dequeued a new buffer.
queued_buffers--;
}
// Get the minimum frames available between the currently playing buffer, and the
// amount we have left to fill
size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
num_frames - frames_written)};
samples_buffer.Pop(&output_buffer[frames_written * frame_size],
frames_available * frame_size);
frames_written += frames_available;
playing_buffer.frames_played += frames_available;
// If that's all the frames in the current buffer, add its samples and mark it as
// consumed
if (playing_buffer.frames_played >= playing_buffer.frames) {
playing_buffer.consumed = true;
}
}
std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
frame_size_bytes);
if (stalled && queued_buffers <= max_queue_size) {
Unstall();
}
}
void SinkStream::Stall() {
if (stalled) {
return;
}
stalled = true;
system.StallProcesses();
}
void SinkStream::Unstall() {
if (!stalled) {
return;
}
system.UnstallProcesses();
stalled = false;
}
} // namespace AudioCore::Sink

View file

@ -3,12 +3,20 @@
#pragma once #pragma once
#include <array>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <span>
#include <vector> #include <vector>
#include "audio_core/common/common.h" #include "audio_core/common/common.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/reader_writer_queue.h"
#include "common/ring_buffer.h"
namespace Core {
class System;
} // namespace Core
namespace AudioCore::Sink { namespace AudioCore::Sink {
@ -34,20 +42,24 @@ struct SinkBuffer {
* You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer
* has been consumed. * has been consumed.
* *
* Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were
* buffers, skipping a buffer will result in all following buffers to never release. * appended, skipping a buffer will result in the queue getting stuck, and all following buffers to
* never release.
* *
* If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this
* is what games do), or call ClearQueue to flush all of the buffers without a full restart. * is what games do), or call ClearQueue to flush all of the buffers without a full restart.
*/ */
class SinkStream { class SinkStream {
public: public:
virtual ~SinkStream() = default; explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {}
virtual ~SinkStream() {
Unstall();
}
/** /**
* Finalize the sink stream. * Finalize the sink stream.
*/ */
virtual void Finalize() = 0; virtual void Finalize() {}
/** /**
* Start the sink stream. * Start the sink stream.
@ -55,48 +67,19 @@ public:
* @param resume - Set to true if this is resuming the stream a previously-active stream. * @param resume - Set to true if this is resuming the stream a previously-active stream.
* Default false. * Default false.
*/ */
virtual void Start(bool resume = false) = 0; virtual void Start(bool resume = false) {}
/** /**
* Stop the sink stream. * Stop the sink stream.
*/ */
virtual void Stop() = 0; virtual void Stop() {}
/**
* Append a new buffer and its samples to a waiting queue to play.
*
* @param buffer - Audio buffer information to be queued.
* @param samples - The s16 samples to be queue for playback.
*/
virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) = 0;
/**
* Release a buffer. Audio In only, will fill a buffer with recorded samples.
*
* @param num_samples - Maximum number of samples to receive.
* @return Vector of recorded samples. May have fewer than num_samples.
*/
virtual std::vector<s16> ReleaseBuffer(u64 num_samples) = 0;
/**
* Check if a certain buffer has been consumed (fully played).
*
* @param tag - Unique tag of a buffer to check for.
* @return True if the buffer has been played, otherwise false.
*/
virtual bool IsBufferConsumed(u64 tag) = 0;
/**
* Empty out the buffer queue.
*/
virtual void ClearQueue() = 0;
/** /**
* Check if the stream is paused. * Check if the stream is paused.
* *
* @return True if paused, otherwise false. * @return True if paused, otherwise false.
*/ */
bool IsPaused() { bool IsPaused() const {
return paused; return paused;
} }
@ -127,34 +110,6 @@ public:
return device_channels; return device_channels;
} }
/**
* Get the total number of samples played by this stream.
*
* @return Number of samples played.
*/
u64 GetPlayedSampleCount() const {
return played_sample_count;
}
/**
* Set the number of samples played.
* This is started and stopped on system start/stop.
*
* @param played_sample_count_ - Number of samples to set.
*/
void SetPlayedSampleCount(u64 played_sample_count_) {
played_sample_count = played_sample_count_;
}
/**
* Add to the played sample count.
*
* @param num_samples - Number of samples to add.
*/
void AddPlayedSampleCount(u64 num_samples) {
played_sample_count += num_samples;
}
/** /**
* Get the system volume. * Get the system volume.
* *
@ -196,27 +151,97 @@ public:
* *
* @return The number of queued buffers. * @return The number of queued buffers.
*/ */
u32 GetQueueSize() { u32 GetQueueSize() const {
return queued_buffers.load(); return queued_buffers.load();
} }
/**
* Set the maximum buffer queue size.
*/
void SetRingSize(u32 ring_size) {
max_queue_size = ring_size;
}
/**
* Append a new buffer and its samples to a waiting queue to play.
*
* @param buffer - Audio buffer information to be queued.
* @param samples - The s16 samples to be queue for playback.
*/
virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples);
/**
* Release a buffer. Audio In only, will fill a buffer with recorded samples.
*
* @param num_samples - Maximum number of samples to receive.
* @return Vector of recorded samples. May have fewer than num_samples.
*/
virtual std::vector<s16> ReleaseBuffer(u64 num_samples);
/**
* Empty out the buffer queue.
*/
void ClearQueue();
/**
* Callback for AudioIn.
*
* @param input_buffer - Input buffer to be filled with samples.
* @param num_frames - Number of frames to be filled.
*/
void ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames);
/**
* Callback for AudioOut and AudioRenderer.
*
* @param output_buffer - Output buffer to be filled with samples.
* @param num_frames - Number of frames to be filled.
*/
void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames);
/**
* Stall core processes if the audio thread falls too far behind.
*/
void Stall();
/**
* Unstall core processes.
*/
void Unstall();
protected: protected:
/// Number of buffers waiting to be played /// Core system
std::atomic<u32> queued_buffers{}; Core::System& system;
/// Total samples played by this stream /// Type of this stream
std::atomic<u64> played_sample_count{}; StreamType type;
/// Set by the audio render/in/out system which uses this stream /// Set by the audio render/in/out system which uses this stream
f32 system_volume{1.0f};
/// Set via IAudioDevice service calls
f32 device_volume{1.0f};
/// Set by the audio render/in/out systen which uses this stream
u32 system_channels{2}; u32 system_channels{2};
/// Channels supported by hardware /// Channels supported by hardware
u32 device_channels{2}; u32 device_channels{2};
/// Is this stream currently paused? /// Is this stream currently paused?
std::atomic<bool> paused{true}; std::atomic<bool> paused{true};
/// Was this stream previously playing? /// Name of this stream
std::atomic<bool> was_playing{false}; std::string name{};
private:
/// Ring buffer of the samples waiting to be played or consumed
Common::RingBuffer<s16, 0x10000> samples_buffer;
/// Audio buffers queued and waiting to play
Common::ReaderWriterQueue<SinkBuffer> queue;
/// The currently-playing audio buffer
SinkBuffer playing_buffer{};
/// The last played (or received) frame of audio, used when the callback underruns
std::array<s16, MaxChannels> last_frame{};
/// Number of buffers waiting to be played
std::atomic<u32> queued_buffers{};
/// The ring size for audio out buffers (usually 4, rarely 2 or 8)
u32 max_queue_size{};
/// Set by the audio render/in/out system which uses this stream
f32 system_volume{1.0f};
/// Set via IAudioDevice service calls
f32 device_volume{1.0f};
/// True if coretiming has been stalled
bool stalled{false};
}; };
using SinkStreamPtr = std::unique_ptr<SinkStream>; using SinkStreamPtr = std::unique_ptr<SinkStream>;

View file

@ -19,7 +19,7 @@ find_package(Git QUIET)
add_custom_command(OUTPUT scm_rev.cpp add_custom_command(OUTPUT scm_rev.cpp
COMMAND ${CMAKE_COMMAND} COMMAND ${CMAKE_COMMAND}
-DSRC_DIR=${CMAKE_SOURCE_DIR} -DSRC_DIR=${PROJECT_SOURCE_DIR}
-DBUILD_REPOSITORY=${BUILD_REPOSITORY} -DBUILD_REPOSITORY=${BUILD_REPOSITORY}
-DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE} -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE}
-DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING} -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING}
@ -31,13 +31,13 @@ add_custom_command(OUTPUT scm_rev.cpp
-DGIT_BRANCH=${GIT_BRANCH} -DGIT_BRANCH=${GIT_BRANCH}
-DBUILD_FULLNAME=${BUILD_FULLNAME} -DBUILD_FULLNAME=${BUILD_FULLNAME}
-DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DGIT_EXECUTABLE=${GIT_EXECUTABLE}
-P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake -P ${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake
DEPENDS DEPENDS
# Check that the scm_rev files haven't changed # Check that the scm_rev files haven't changed
"${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in"
"${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h"
# technically we should regenerate if the git version changed, but its not worth the effort imo # technically we should regenerate if the git version changed, but its not worth the effort imo
"${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" "${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
VERBATIM VERBATIM
) )
@ -166,6 +166,7 @@ if(ARCHITECTURE_x86_64)
x64/xbyak_abi.h x64/xbyak_abi.h
x64/xbyak_util.h x64/xbyak_util.h
) )
target_link_libraries(common PRIVATE xbyak)
endif() endif()
if (MSVC) if (MSVC)
@ -189,7 +190,7 @@ endif()
create_target_directory_groups(common) create_target_directory_groups(common)
target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads)
target_link_libraries(common PRIVATE lz4::lz4 xbyak) target_link_libraries(common PRIVATE lz4::lz4)
if (TARGET zstd::zstd) if (TARGET zstd::zstd)
target_link_libraries(common PRIVATE zstd::zstd) target_link_libraries(common PRIVATE zstd::zstd)
else() else()

View file

@ -16,6 +16,7 @@ namespace AnnounceMultiplayerRoom {
struct GameInfo { struct GameInfo {
std::string name{""}; std::string name{""};
u64 id{0}; u64 id{0};
std::string version{""};
}; };
struct Member { struct Member {

View file

@ -102,6 +102,8 @@ struct AnalogProperties {
float offset{}; float offset{};
// Invert direction of the sensor data // Invert direction of the sensor data
bool inverted{}; bool inverted{};
// Press once to activate, press again to release
bool toggle{};
}; };
// Single analog sensor data // Single analog sensor data
@ -115,8 +117,11 @@ struct AnalogStatus {
struct ButtonStatus { struct ButtonStatus {
Common::UUID uuid{}; Common::UUID uuid{};
bool value{}; bool value{};
// Invert value of the button
bool inverted{}; bool inverted{};
// Press once to activate, press again to release
bool toggle{}; bool toggle{};
// Internal lock for the toggle status
bool locked{}; bool locked{};
}; };

View file

@ -11,7 +11,7 @@ namespace Common {
namespace detail { namespace detail {
template <typename T, size_t Size, size_t Align> template <typename T, size_t Size, size_t Align>
struct TypedStorageImpl { struct TypedStorageImpl {
std::aligned_storage_t<Size, Align> storage_; alignas(Align) u8 storage_[Size];
}; };
} // namespace detail } // namespace detail

View file

@ -195,6 +195,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.shader_backend.SetGlobal(true); values.shader_backend.SetGlobal(true);
values.use_asynchronous_shaders.SetGlobal(true); values.use_asynchronous_shaders.SetGlobal(true);
values.use_fast_gpu_time.SetGlobal(true); values.use_fast_gpu_time.SetGlobal(true);
values.use_pessimistic_flushes.SetGlobal(true);
values.bg_red.SetGlobal(true); values.bg_red.SetGlobal(true);
values.bg_green.SetGlobal(true); values.bg_green.SetGlobal(true);
values.bg_blue.SetGlobal(true); values.bg_blue.SetGlobal(true);

View file

@ -446,6 +446,7 @@ struct Values {
ShaderBackend::SPIRV, "shader_backend"}; ShaderBackend::SPIRV, "shader_backend"};
SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"}; SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"}; SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
SwitchableSetting<bool> use_pessimistic_flushes{false, "use_pessimistic_flushes"};
SwitchableSetting<u8> bg_red{0, "bg_red"}; SwitchableSetting<u8> bg_red{0, "bg_red"};
SwitchableSetting<u8> bg_green{0, "bg_green"}; SwitchableSetting<u8> bg_green{0, "bg_green"};
@ -529,6 +530,7 @@ struct Values {
Setting<bool> use_debug_asserts{false, "use_debug_asserts"}; Setting<bool> use_debug_asserts{false, "use_debug_asserts"};
Setting<bool> use_auto_stub{false, "use_auto_stub"}; Setting<bool> use_auto_stub{false, "use_auto_stub"};
Setting<bool> enable_all_controllers{false, "enable_all_controllers"}; Setting<bool> enable_all_controllers{false, "enable_all_controllers"};
Setting<bool> create_crash_dumps{false, "create_crash_dumps"};
// Miscellaneous // Miscellaneous
Setting<std::string> log_filter{"*:Info", "log_filter"}; Setting<std::string> log_filter{"*:Info", "log_filter"};

View file

@ -54,6 +54,10 @@ public:
is_set = false; is_set = false;
} }
[[nodiscard]] bool IsSet() {
return is_set;
}
private: private:
std::condition_variable condvar; std::condition_variable condvar;
std::mutex mutex; std::mutex mutex;

View file

@ -2,16 +2,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
add_library(core STATIC add_library(core STATIC
announce_multiplayer_session.cpp
announce_multiplayer_session.h
arm/arm_interface.h arm/arm_interface.h
arm/arm_interface.cpp arm/arm_interface.cpp
arm/dynarmic/arm_dynarmic_32.cpp
arm/dynarmic/arm_dynarmic_32.h
arm/dynarmic/arm_dynarmic_64.cpp
arm/dynarmic/arm_dynarmic_64.h
arm/dynarmic/arm_dynarmic_cp15.cpp
arm/dynarmic/arm_dynarmic_cp15.h
arm/dynarmic/arm_exclusive_monitor.cpp arm/dynarmic/arm_exclusive_monitor.cpp
arm/dynarmic/arm_exclusive_monitor.h arm/dynarmic/arm_exclusive_monitor.h
arm/exclusive_monitor.cpp arm/exclusive_monitor.cpp
@ -527,6 +519,9 @@ add_library(core STATIC
hle/service/ncm/ncm.h hle/service/ncm/ncm.h
hle/service/nfc/nfc.cpp hle/service/nfc/nfc.cpp
hle/service/nfc/nfc.h hle/service/nfc/nfc.h
hle/service/nfp/amiibo_crypto.cpp
hle/service/nfp/amiibo_crypto.h
hle/service/nfp/amiibo_types.h
hle/service/nfp/nfp.cpp hle/service/nfp/nfp.cpp
hle/service/nfp/nfp.h hle/service/nfp/nfp.h
hle/service/nfp/nfp_user.cpp hle/service/nfp/nfp_user.cpp
@ -540,14 +535,14 @@ add_library(core STATIC
hle/service/npns/npns.cpp hle/service/npns/npns.cpp
hle/service/npns/npns.h hle/service/npns/npns.h
hle/service/ns/errors.h hle/service/ns/errors.h
hle/service/ns/iplatform_service_manager.cpp
hle/service/ns/iplatform_service_manager.h
hle/service/ns/language.cpp hle/service/ns/language.cpp
hle/service/ns/language.h hle/service/ns/language.h
hle/service/ns/ns.cpp hle/service/ns/ns.cpp
hle/service/ns/ns.h hle/service/ns/ns.h
hle/service/ns/pdm_qry.cpp hle/service/ns/pdm_qry.cpp
hle/service/ns/pdm_qry.h hle/service/ns/pdm_qry.h
hle/service/ns/pl_u.cpp
hle/service/ns/pl_u.h
hle/service/nvdrv/devices/nvdevice.h hle/service/nvdrv/devices/nvdevice.h
hle/service/nvdrv/devices/nvdisp_disp0.cpp hle/service/nvdrv/devices/nvdisp_disp0.cpp
hle/service/nvdrv/devices/nvdisp_disp0.h hle/service/nvdrv/devices/nvdisp_disp0.h

View file

@ -141,8 +141,6 @@ struct System::Impl {
core_timing.SyncPause(false); core_timing.SyncPause(false);
is_paused = false; is_paused = false;
audio_core->PauseSinks(false);
return status; return status;
} }
@ -150,8 +148,6 @@ struct System::Impl {
std::unique_lock<std::mutex> lk(suspend_guard); std::unique_lock<std::mutex> lk(suspend_guard);
status = SystemResultStatus::Success; status = SystemResultStatus::Success;
audio_core->PauseSinks(true);
core_timing.SyncPause(true); core_timing.SyncPause(true);
kernel.Suspend(true); kernel.Suspend(true);
is_paused = true; is_paused = true;
@ -319,10 +315,19 @@ struct System::Impl {
if (app_loader->ReadTitle(name) != Loader::ResultStatus::Success) { if (app_loader->ReadTitle(name) != Loader::ResultStatus::Success) {
LOG_ERROR(Core, "Failed to read title for ROM (Error {})", load_result); LOG_ERROR(Core, "Failed to read title for ROM (Error {})", load_result);
} }
std::string title_version;
const FileSys::PatchManager pm(program_id, system.GetFileSystemController(),
system.GetContentProvider());
const auto metadata = pm.GetControlMetadata();
if (metadata.first != nullptr) {
title_version = metadata.first->GetVersionString();
}
if (auto room_member = room_network.GetRoomMember().lock()) { if (auto room_member = room_network.GetRoomMember().lock()) {
Network::GameInfo game_info; Network::GameInfo game_info;
game_info.name = name; game_info.name = name;
game_info.id = program_id; game_info.id = program_id;
game_info.version = title_version;
room_member->SendGameInfo(game_info); room_member->SendGameInfo(game_info);
} }

View file

@ -73,7 +73,6 @@ void CoreTiming::Shutdown() {
if (timer_thread) { if (timer_thread) {
timer_thread->join(); timer_thread->join();
} }
pause_callbacks.clear();
ClearPendingEvents(); ClearPendingEvents();
timer_thread.reset(); timer_thread.reset();
has_started = false; has_started = false;
@ -86,10 +85,6 @@ void CoreTiming::Pause(bool is_paused) {
if (!is_paused) { if (!is_paused) {
pause_end_time = GetGlobalTimeNs().count(); pause_end_time = GetGlobalTimeNs().count();
} }
for (auto& cb : pause_callbacks) {
cb(is_paused);
}
} }
void CoreTiming::SyncPause(bool is_paused) { void CoreTiming::SyncPause(bool is_paused) {
@ -110,10 +105,6 @@ void CoreTiming::SyncPause(bool is_paused) {
if (!is_paused) { if (!is_paused) {
pause_end_time = GetGlobalTimeNs().count(); pause_end_time = GetGlobalTimeNs().count();
} }
for (auto& cb : pause_callbacks) {
cb(is_paused);
}
} }
bool CoreTiming::IsRunning() const { bool CoreTiming::IsRunning() const {
@ -143,13 +134,17 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
std::chrono::nanoseconds resched_time, std::chrono::nanoseconds resched_time,
const std::shared_ptr<EventType>& event_type, const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data, bool absolute_time) { std::uintptr_t user_data, bool absolute_time) {
std::scoped_lock scope{basic_lock}; {
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; std::scoped_lock scope{basic_lock};
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
event_queue.emplace_back( event_queue.emplace_back(
Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
event.Set();
} }
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
@ -219,11 +214,6 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
} }
} }
void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) {
std::scoped_lock lock{basic_lock};
pause_callbacks.emplace_back(std::move(callback));
}
std::optional<s64> CoreTiming::Advance() { std::optional<s64> CoreTiming::Advance() {
std::scoped_lock lock{advance_lock, basic_lock}; std::scoped_lock lock{advance_lock, basic_lock};
global_timer = GetGlobalTimeNs().count(); global_timer = GetGlobalTimeNs().count();
@ -243,17 +233,17 @@ std::optional<s64> CoreTiming::Advance() {
basic_lock.lock(); basic_lock.lock();
if (evt.reschedule_time != 0) { if (evt.reschedule_time != 0) {
// If this event was scheduled into a pause, its time now is going to be way behind.
// Re-set this event to continue from the end of the pause.
auto next_time{evt.time + evt.reschedule_time};
if (evt.time < pause_end_time) {
next_time = pause_end_time + evt.reschedule_time;
}
const auto next_schedule_time{new_schedule_time.has_value() const auto next_schedule_time{new_schedule_time.has_value()
? new_schedule_time.value().count() ? new_schedule_time.value().count()
: evt.reschedule_time}; : evt.reschedule_time};
// If this event was scheduled into a pause, its time now is going to be way behind.
// Re-set this event to continue from the end of the pause.
auto next_time{evt.time + next_schedule_time};
if (evt.time < pause_end_time) {
next_time = pause_end_time + next_schedule_time;
}
event_queue.emplace_back( event_queue.emplace_back(
Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time}); Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
@ -264,8 +254,7 @@ std::optional<s64> CoreTiming::Advance() {
} }
if (!event_queue.empty()) { if (!event_queue.empty()) {
const s64 next_time = event_queue.front().time - global_timer; return event_queue.front().time;
return next_time;
} else { } else {
return std::nullopt; return std::nullopt;
} }
@ -278,11 +267,29 @@ void CoreTiming::ThreadLoop() {
paused_set = false; paused_set = false;
const auto next_time = Advance(); const auto next_time = Advance();
if (next_time) { if (next_time) {
if (*next_time > 0) { // There are more events left in the queue, wait until the next event.
std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time); const auto wait_time = *next_time - GetGlobalTimeNs().count();
event.WaitFor(next_time_ns); if (wait_time > 0) {
// Assume a timer resolution of 1ms.
static constexpr s64 TimerResolutionNS = 1000000;
// Sleep in discrete intervals of the timer resolution, and spin the rest.
const auto sleep_time = wait_time - (wait_time % TimerResolutionNS);
if (sleep_time > 0) {
event.WaitFor(std::chrono::nanoseconds(sleep_time));
}
while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) {
// Yield to reduce thread starvation.
std::this_thread::yield();
}
if (event.IsSet()) {
event.Reset();
}
} }
} else { } else {
// Queue is empty, wait until another event is scheduled and signals us to continue.
wait_set = true; wait_set = true;
event.Wait(); event.Wait();
} }

View file

@ -22,7 +22,6 @@ namespace Core::Timing {
/// A callback that may be scheduled for a particular core timing event. /// A callback that may be scheduled for a particular core timing event.
using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>( using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>; std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>;
using PauseCallback = std::function<void(bool paused)>;
/// Contains the characteristics of a particular event. /// Contains the characteristics of a particular event.
struct EventType { struct EventType {
@ -134,9 +133,6 @@ public:
/// Checks for events manually and returns time in nanoseconds for next event, threadsafe. /// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
std::optional<s64> Advance(); std::optional<s64> Advance();
/// Register a callback function to be called when coretiming pauses.
void RegisterPauseCallback(PauseCallback&& callback);
private: private:
struct Event; struct Event;
@ -176,8 +172,6 @@ private:
/// Cycle timing /// Cycle timing
u64 ticks{}; u64 ticks{};
s64 downcount{}; s64 downcount{};
std::vector<PauseCallback> pause_callbacks{};
}; };
/// Creates a core timing event with the given name and callback. /// Creates a core timing event with the given name and callback.

View file

@ -9,7 +9,7 @@
#include "core/file_sys/system_archive/data/font_standard.h" #include "core/file_sys/system_archive/data/font_standard.h"
#include "core/file_sys/system_archive/shared_font.h" #include "core/file_sys/system_archive/shared_font.h"
#include "core/file_sys/vfs_vector.h" #include "core/file_sys/vfs_vector.h"
#include "core/hle/service/ns/pl_u.h" #include "core/hle/service/ns/iplatform_service_manager.h"
namespace FileSys::SystemArchive { namespace FileSys::SystemArchive {

View file

@ -562,6 +562,16 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
return; return;
} }
// GC controllers have triggers not buttons
if (npad_type == NpadStyleIndex::GameCube) {
if (index == Settings::NativeButton::ZR) {
return;
}
if (index == Settings::NativeButton::ZL) {
return;
}
}
switch (index) { switch (index) {
case Settings::NativeButton::A: case Settings::NativeButton::A:
controller.npad_button_state.a.Assign(current_status.value); controller.npad_button_state.a.Assign(current_status.value);
@ -738,6 +748,11 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
return; return;
} }
// Only GC controllers have analog triggers
if (npad_type != NpadStyleIndex::GameCube) {
return;
}
const auto& trigger = controller.trigger_values[index]; const auto& trigger = controller.trigger_values[index];
switch (index) { switch (index) {

Some files were not shown because too many files have changed in this diff Show more