From 72f514f3506845da9c39201bb22afda07e83ac19 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:48:55 +0800 Subject: [PATCH] Trophy: Replace sdl mixer library with minimp3 header for playing trophy sounds (#4261) * Replace sdl mixer library with minimp3 header * clang * set spec before putting in audiostream * respect main audio output device setting * fixup * replace file with submodule * cleanup * capitalize functions like the others * move buffer to heap * use vector for pcm buffer instead --- .gitmodules | 8 +- CMakeLists.txt | 9 +- externals/CMakeLists.txt | 15 +-- externals/minimp3 | 1 + externals/sdl3_mixer | 1 - src/core/libraries/np/trophy_ui.cpp | 164 ++++++++++++++++++++++------ src/core/libraries/np/trophy_ui.h | 15 +-- 7 files changed, 149 insertions(+), 64 deletions(-) create mode 160000 externals/minimp3 delete mode 160000 externals/sdl3_mixer diff --git a/.gitmodules b/.gitmodules index 40f293a32..660bb3046 100644 --- a/.gitmodules +++ b/.gitmodules @@ -106,10 +106,6 @@ [submodule "externals/json"] path = externals/json url = https://github.com/nlohmann/json.git -[submodule "externals/sdl3_mixer"] - path = externals/sdl3_mixer - url = https://github.com/libsdl-org/SDL_mixer - shallow = true [submodule "externals/miniz"] path = externals/miniz url = https://github.com/richgel999/miniz @@ -136,3 +132,7 @@ path = externals/libusb url = https://github.com/shadexternals/libusb.git branch = shadps4 +[submodule "externals/minimp3"] + path = externals/minimp3 + url = https://github.com/lieff/minimp3 + shallow = true diff --git a/CMakeLists.txt b/CMakeLists.txt index 3211f9061..5f29e03a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,11 +233,8 @@ find_package(nlohmann_json 3.12 CONFIG) find_package(PNG 1.6 MODULE) find_package(OpenAL CONFIG) find_package(RenderDoc 1.6.0 MODULE) -find_package(SDL3_mixer 2.8.1 CONFIG) find_package(SDL3_image CONFIG) -if (SDL3_mixer_FOUND) - find_package(SDL3 3.1.2 CONFIG) -endif() +find_package(SDL3 3.1.2 CONFIG) find_package(stb MODULE) find_package(toml11 4.2.0 CONFIG) find_package(tsl-robin-map 1.3.0 CONFIG) @@ -1146,8 +1143,8 @@ add_executable(shadps4 create_target_directory_groups(shadps4) -target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) -target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer pugixml::pugixml) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG minimp3) +target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_image::SDL3_image pugixml::pugixml) target_link_libraries(shadps4 PRIVATE stb::headers lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib) if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 6fc3e471c..735552485 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -86,18 +86,6 @@ if (NOT TARGET SDL3_image::SDL3_image) add_subdirectory(sdl3_image) endif() -# SDL3_mixer -if (NOT TARGET SDL3_mixer::SDL3_mixer) - set(SDLMIXER_FLAC OFF) - set(SDLMIXER_OGG OFF) - set(SDLMIXER_MOD OFF) - set(SDLMIXER_MIDI OFF) - set(SDLMIXER_OPUS OFF) - set(SDLMIXER_WAVPACK OFF) - set(BUILD_SHARED_LIBS OFF) - add_subdirectory(sdl3_mixer) -endif() - # vulkan-headers if (NOT TARGET Vulkan::Headers) set(VULKAN_HEADERS_ENABLE_MODULE OFF) @@ -311,6 +299,9 @@ if (NOT TARGET CLI11::CLI11) add_subdirectory(CLI11) endif() +# minimp3 +add_library(minimp3 INTERFACE) +target_include_directories(minimp3 INTERFACE minimp3) #openal if (NOT TARGET OpenAL::OpenAL) diff --git a/externals/minimp3 b/externals/minimp3 new file mode 160000 index 000000000..7b590fdcf --- /dev/null +++ b/externals/minimp3 @@ -0,0 +1 @@ +Subproject commit 7b590fdcfa5a79c033e76eacc05d0c3e4c79f536 diff --git a/externals/sdl3_mixer b/externals/sdl3_mixer deleted file mode 160000 index 4182794ea..000000000 --- a/externals/sdl3_mixer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4182794ea45fe28568728670c6f1583855d0e85c diff --git a/src/core/libraries/np/trophy_ui.cpp b/src/core/libraries/np/trophy_ui.cpp index a4fd21a33..d277a6e3c 100644 --- a/src/core/libraries/np/trophy_ui.cpp +++ b/src/core/libraries/np/trophy_ui.cpp @@ -1,15 +1,18 @@ // SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include #include #include +#include #include #include -#include "common/assert.h" +#include + +#define MINIMP3_IMPLEMENTATION +#include + #include "common/path_util.h" -#include "common/singleton.h" #include "core/emulator_settings.h" #include "core/libraries/np/trophy_ui.h" #include "imgui/imgui_std.h" @@ -22,9 +25,7 @@ namespace Libraries::Np::NpTrophy { std::optional current_trophy_ui; std::queue trophy_queue; std::mutex queueMtx; - std::string side = "right"; - double trophy_timer; TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::string& trophyName, @@ -32,7 +33,6 @@ TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::strin : trophy_name(trophyName), trophy_type(rarity) { side = EmulatorSettings.GetTrophyNotificationSide(); - trophy_timer = EmulatorSettings.GetTrophyNotificationDuration(); if (std::filesystem::exists(trophyIconPath)) { @@ -86,44 +86,86 @@ TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::strin AddLayer(this); - MIX_Init(); - mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL); - if (!mixer) { - LOG_ERROR(Lib_NpTrophy, "Could not initialize SDL Mixer, {}", SDL_GetError()); + if (SDL_WasInit(SDL_INIT_AUDIO) != 0) { + if (!SDL_Init(SDL_INIT_AUDIO)) { + LOG_ERROR(Lib_NpTrophy, "Unable to init SDL Audio for trophy sound: {}", + SDL_GetError()); + return; + } + } + + audioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr); + + // user selected Sdl Backend, use same device as Sdl main Device + if (EmulatorSettings.GetAudioBackend() == 0) { + if (EmulatorSettings.GetSDLMainOutputDevice() != "Default Device") { + int count; + SDL_AudioDeviceID* devices = SDL_GetAudioPlaybackDevices(&count); + + for (int i = 0; i < count; i++) { + std::string name = SDL_GetAudioDeviceName(devices[i]); + if (name == EmulatorSettings.GetSDLMainOutputDevice()) { + audioDevice = SDL_OpenAudioDevice(devices[i], NULL); + } + } + } + + // user selected OpenAl Backend, use same device as OpenAl main Device + } else if (EmulatorSettings.GetAudioBackend() == 1) { + if (EmulatorSettings.GetOpenALMainOutputDevice() != "Default Device") { + int count; + SDL_AudioDeviceID* devices = SDL_GetAudioPlaybackDevices(&count); + + for (int i = 0; i < count; i++) { + std::string name = SDL_GetAudioDeviceName(devices[i]); + // Device names are the same for openAl/Sdl, just with an added prefix + name.erase(0, 15); + if (name == EmulatorSettings.GetOpenALMainOutputDevice()) { + audioDevice = SDL_OpenAudioDevice(devices[i], NULL); + } + } + } + } + + if (audioDevice == 0) { + LOG_ERROR(Lib_NpTrophy, "Unable to open audio device for trophy sound playback: {}", + SDL_GetError()); return; } - MIX_SetMasterGain(mixer, static_cast(EmulatorSettings.GetVolumeSlider() / 100.f)); - auto musicPathMp3 = CustomTrophy_Dir / "trophy.mp3"; - auto musicPathWav = CustomTrophy_Dir / "trophy.wav"; + const auto musicPathMp3 = CustomTrophy_Dir / "trophy.mp3"; + const auto musicPathWav = CustomTrophy_Dir / "trophy.wav"; + std::vector sound_data; if (std::filesystem::exists(musicPathMp3)) { - audio = MIX_LoadAudio(mixer, musicPathMp3.string().c_str(), false); + std::ifstream file(musicPathMp3, std::ios::binary); + sound_data = std::vector((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + file.close(); + PlayMp3(sound_data); } else if (std::filesystem::exists(musicPathWav)) { - audio = MIX_LoadAudio(mixer, musicPathWav.string().c_str(), false); + std::ifstream file(musicPathWav, std::ios::binary); + sound_data = std::vector((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + file.close(); + PlayWav(sound_data); } else { auto soundFile = resource.open("src/images/trophy.wav"); - std::vector soundData = std::vector(soundFile.begin(), soundFile.end()); - audio = - MIX_LoadAudio_IO(mixer, SDL_IOFromMem(soundData.data(), soundData.size()), false, true); - // due to low volume of default sound file - MIX_SetMasterGain(mixer, MIX_GetMasterGain(mixer) * 1.3f); - } - - if (!audio) { - LOG_ERROR(Lib_NpTrophy, "Could not loud audio file, {}", SDL_GetError()); - return; - } - - if (!MIX_PlayAudio(mixer, audio)) { - LOG_ERROR(Lib_NpTrophy, "Could not play audio file, {}", SDL_GetError()); + sound_data = std::vector(soundFile.begin(), soundFile.end()); + PlayWav(sound_data); } } TrophyUI::~TrophyUI() { - MIX_DestroyAudio(audio); - MIX_DestroyMixer(mixer); - MIX_Quit(); + if (stream) { + SDL_DestroyAudioStream(stream); + } + + // if emulator is not using sdl audio backend + if (EmulatorSettings.GetAudioBackend() != 0) { + SDL_CloseAudioDevice(audioDevice); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + } Finish(); } @@ -275,6 +317,64 @@ void TrophyUI::Draw() { } } +void TrophyUI::PlayMp3(std::vector mp3Data) { + mp3dec_t mp3d; + mp3dec_frame_info_t info; + std::vector pcm(MINIMP3_MAX_SAMPLES_PER_FRAME); + mp3dec_init(&mp3d); + + // always s16 when decoded by minimp3, channels/frequency changed later on as necessary + SDL_AudioSpec spec = {SDL_AUDIO_S16, 2, 44100}; + bool specInfoSet = false; + + stream = SDL_CreateAudioStream(&spec, &spec); + SDL_BindAudioStream(audioDevice, stream); + + // make this louder than game stream + SDL_SetAudioStreamGain(stream, + static_cast(EmulatorSettings.GetVolumeSlider() * 0.01f * 1.2f)); + unsigned char* buffer_ptr = mp3Data.data(); + size_t remaining_size = mp3Data.size(); + + while (remaining_size > 0) { + int samples = mp3dec_decode_frame(&mp3d, buffer_ptr, remaining_size, pcm.data(), &info); + if (samples > 0) { + if (!specInfoSet && info.hz > 0 && info.channels > 0) { + spec = {SDL_AUDIO_S16, info.channels, info.hz}; + SDL_SetAudioStreamFormat(stream, &spec, &spec); + specInfoSet = true; + } + + SDL_PutAudioStreamData(stream, pcm.data(), samples * 2 * sizeof(short)); + buffer_ptr += info.frame_bytes; + remaining_size -= info.frame_bytes; + } else { + break; + } + } +} + +void TrophyUI::PlayWav(std::vector wavData) { + SDL_AudioSpec spec; + Uint8* audioBuf = nullptr; + Uint32 audioLen = 0; + + SDL_IOStream* io = SDL_IOFromConstMem(wavData.data(), wavData.size()); + if (!SDL_LoadWAV_IO(io, true, &spec, &audioBuf, &audioLen)) { + LOG_ERROR(Lib_NpTrophy, "Unable to load trophy wave file data: {}", SDL_GetError()); + return; + } + + SDL_AudioStream* stream = SDL_CreateAudioStream(&spec, &spec); + SDL_BindAudioStream(audioDevice, stream); + + // make this louder than game stream + SDL_SetAudioStreamGain(stream, + static_cast(EmulatorSettings.GetVolumeSlider() * 0.01f * 1.2f)); + SDL_PutAudioStreamData(stream, audioBuf, audioLen); + SDL_free(audioBuf); +} + void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::string& trophyName, const std::string_view& rarity) { std::lock_guard lock(queueMtx); diff --git a/src/core/libraries/np/trophy_ui.h b/src/core/libraries/np/trophy_ui.h index 2734471b3..730c8125c 100644 --- a/src/core/libraries/np/trophy_ui.h +++ b/src/core/libraries/np/trophy_ui.h @@ -4,13 +4,8 @@ #pragma once #include -#include -#include -#include +#include -#include "common/fixed_value.h" -#include "common/types.h" -#include "core/libraries/np/np_trophy.h" #include "imgui/imgui_layer.h" #include "imgui/imgui_texture.h" @@ -27,13 +22,15 @@ public: void Draw() override; private: + void PlayMp3(std::vector mp3Data); + void PlayWav(std::vector wavData); + std::string trophy_name; std::string_view trophy_type; ImGui::RefCountedTexture trophy_icon; ImGui::RefCountedTexture trophy_type_icon; - - MIX_Mixer* mixer; - MIX_Audio* audio; + SDL_AudioStream* stream; + SDL_AudioDeviceID audioDevice; }; struct TrophyInfo {