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
This commit is contained in:
rainmakerv2 2026-04-15 15:48:55 +08:00 committed by GitHub
parent f242655fbb
commit 72f514f350
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 149 additions and 64 deletions

8
.gitmodules vendored
View File

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

View File

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

View File

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

1
externals/minimp3 vendored Submodule

@ -0,0 +1 @@
Subproject commit 7b590fdcfa5a79c033e76eacc05d0c3e4c79f536

@ -1 +0,0 @@
Subproject commit 4182794ea45fe28568728670c6f1583855d0e85c

View File

@ -1,15 +1,18 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <filesystem>
#include <fstream>
#include <mutex>
#include <SDL3/SDL_init.h>
#include <cmrc/cmrc.hpp>
#include <imgui.h>
#include "common/assert.h"
#include <queue>
#define MINIMP3_IMPLEMENTATION
#include <minimp3.h>
#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<TrophyUI> current_trophy_ui;
std::queue<TrophyInfo> 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<float>(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<unsigned char> 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<unsigned char>((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
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<unsigned char>((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
file.close();
PlayWav(sound_data);
} else {
auto soundFile = resource.open("src/images/trophy.wav");
std::vector<u8> soundData = std::vector<u8>(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<unsigned char>(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<unsigned char> mp3Data) {
mp3dec_t mp3d;
mp3dec_frame_info_t info;
std::vector<short> 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<float>(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<unsigned char> 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<float>(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<std::mutex> lock(queueMtx);

View File

@ -4,13 +4,8 @@
#pragma once
#include <string>
#include <variant>
#include <SDL3_mixer/SDL_mixer.h>
#include <queue>
#include <SDL3/SDL_audio.h>
#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<unsigned char> mp3Data);
void PlayWav(std::vector<unsigned char> 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 {