diff --git a/rpcs3/Emu/Cell/Modules/cellMusic.cpp b/rpcs3/Emu/Cell/Modules/cellMusic.cpp index caca077a32..e35895c6e4 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusic.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusic.cpp @@ -3,6 +3,9 @@ #include "Emu/Cell/lv2/sys_lwmutex.h" #include "Emu/Cell/lv2/sys_lwcond.h" #include "Emu/Cell/lv2/sys_spu.h" +#include "Emu/Io/music_handler_base.h" +#include "Emu/System.h" +#include "Emu/VFS.h" #include "cellSearch.h" #include "cellSpurs.h" #include "cellSysutil.h" @@ -67,6 +70,14 @@ struct music_state vm::ptr param, vm::ptr userData)> func{}; vm::ptr userData{}; + std::mutex mtx; + std::shared_ptr handler; + music_selection_context current_selection_context; + + music_state() + { + handler = Emu.GetCallbacks().get_music_handler(); + } }; error_code cellMusicGetSelectionContext(vm::ptr context) @@ -75,6 +86,12 @@ error_code cellMusicGetSelectionContext(vm::ptr conte if (!context) return CELL_MUSIC_ERROR_PARAM; + + auto& music = g_fxo->get(); + std::lock_guard lock(music.mtx); + + *context = music.current_selection_context.get(); + cellMusic.success("cellMusicGetSelectionContext: selection context = %s", music.current_selection_context.to_string()); return CELL_OK; } @@ -93,7 +110,17 @@ error_code cellMusicSetSelectionContext2(vm::ptr cont sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32 { - music.func(ppu, CELL_MUSIC2_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(CELL_OK), music.userData); + bool result = false; + { + std::lock_guard lock(music.mtx); + result = music.current_selection_context.set(*context); + } + const u32 status = result ? u32{CELL_OK} : u32{CELL_MUSIC2_ERROR_INVALID_CONTEXT}; + + if (result) cellMusic.success("cellMusicSetSelectionContext2: new selection context = %s)", music.current_selection_context.to_string()); + else cellMusic.todo("cellMusicSetSelectionContext2: failed. context = %s)", context->data); + + music.func(ppu, CELL_MUSIC2_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(status), music.userData); return CELL_OK; }); @@ -111,6 +138,8 @@ error_code cellMusicSetVolume2(f32 level) if (!music.func) return CELL_MUSIC2_ERROR_GENERIC; + music.handler->set_volume(level); + sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32 { music.func(ppu, CELL_MUSIC2_EVENT_SET_VOLUME_RESULT, vm::addr_t(CELL_OK), music.userData); @@ -127,7 +156,10 @@ error_code cellMusicGetContentsId(vm::ptr contents_id) if (!contents_id) return CELL_MUSIC_ERROR_PARAM; - return CELL_OK; + // HACKY + auto& music = g_fxo->get(); + std::lock_guard lock(music.mtx); + return music.current_selection_context.find_content_id(contents_id); } error_code cellMusicSetSelectionContext(vm::ptr context) @@ -144,7 +176,17 @@ error_code cellMusicSetSelectionContext(vm::ptr conte sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32 { - music.func(ppu, CELL_MUSIC_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(CELL_OK), music.userData); + bool result = false; + { + std::lock_guard lock(music.mtx); + result = music.current_selection_context.set(*context); + } + const u32 status = result ? u32{CELL_OK} : u32{CELL_MUSIC_ERROR_INVALID_CONTEXT}; + + if (result) cellMusic.success("cellMusicSetSelectionContext: new selection context = %s)", music.current_selection_context.to_string()); + else cellMusic.todo("cellMusicSetSelectionContext: failed. context = %s)", context->data); + + music.func(ppu, CELL_MUSIC_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(status), music.userData); return CELL_OK; }); @@ -184,6 +226,9 @@ error_code cellMusicGetPlaybackStatus2(vm::ptr status) if (!status) return CELL_MUSIC2_ERROR_PARAM; + const auto& music = g_fxo->get(); + *status = music.handler->get_state(); + return CELL_OK; } @@ -194,7 +239,10 @@ error_code cellMusicGetContentsId2(vm::ptr contents_id) if (!contents_id) return CELL_MUSIC2_ERROR_PARAM; - return CELL_OK; + // HACKY + auto& music = g_fxo->get(); + std::lock_guard lock(music.mtx); + return music.current_selection_context.find_content_id(contents_id); } error_code cellMusicFinalize() @@ -292,6 +340,11 @@ error_code cellMusicGetSelectionContext2(vm::ptr cont if (!context) return CELL_MUSIC2_ERROR_PARAM; + auto& music = g_fxo->get(); + std::lock_guard lock(music.mtx); + *context = music.current_selection_context.get(); + cellMusic.success("cellMusicGetSelectionContext2: selection context = %s", music.current_selection_context.to_string()); + return CELL_OK; } @@ -302,6 +355,9 @@ error_code cellMusicGetVolume(vm::ptr level) if (!level) return CELL_MUSIC_ERROR_PARAM; + const auto& music = g_fxo->get(); + *level = music.handler->get_volume(); + return CELL_OK; } @@ -312,6 +368,9 @@ error_code cellMusicGetPlaybackStatus(vm::ptr status) if (!status) return CELL_MUSIC_ERROR_PARAM; + const auto& music = g_fxo->get(); + *status = music.handler->get_state(); + return CELL_OK; } @@ -319,7 +378,7 @@ error_code cellMusicSetPlaybackCommand2(s32 command, vm::ptr param) { cellMusic.todo("cellMusicSetPlaybackCommand2(command=0x%x, param=*0x%x)", command, param); - if (command < CELL_MUSIC_PB_CMD_STOP || command > CELL_MUSIC_PB_CMD_FASTREVERSE) + if (command < CELL_MUSIC2_PB_CMD_STOP || command > CELL_MUSIC2_PB_CMD_FASTREVERSE) return CELL_MUSIC2_ERROR_PARAM; auto& music = g_fxo->get(); @@ -329,6 +388,40 @@ error_code cellMusicSetPlaybackCommand2(s32 command, vm::ptr param) sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32 { + // TODO: play proper song when the context is a playlist + std::string path; + { + std::lock_guard lock(music.mtx); + path = vfs::get(music.current_selection_context.path); + } + + switch (command) + { + case CELL_MUSIC2_PB_CMD_STOP: + music.handler->stop(); + break; + case CELL_MUSIC2_PB_CMD_PLAY: + music.handler->play(path); + break; + case CELL_MUSIC2_PB_CMD_PAUSE: + music.handler->pause(); + break; + case CELL_MUSIC2_PB_CMD_NEXT: + music.handler->play(path); + break; + case CELL_MUSIC2_PB_CMD_PREV: + music.handler->play(path); + break; + case CELL_MUSIC2_PB_CMD_FASTFORWARD: + music.handler->fast_forward(); + break; + case CELL_MUSIC2_PB_CMD_FASTREVERSE: + music.handler->fast_reverse(); + break; + default: + break; + } + music.func(ppu, CELL_MUSIC2_EVENT_SET_PLAYBACK_COMMAND_RESULT, vm::addr_t(CELL_OK), music.userData); return CELL_OK; }); @@ -350,6 +443,40 @@ error_code cellMusicSetPlaybackCommand(s32 command, vm::ptr param) sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32 { + // TODO: play proper song when the context is a playlist + std::string path; + { + std::lock_guard lock(music.mtx); + path = vfs::get(music.current_selection_context.path); + } + + switch (command) + { + case CELL_MUSIC_PB_CMD_STOP: + music.handler->stop(); + break; + case CELL_MUSIC_PB_CMD_PLAY: + music.handler->play(path); + break; + case CELL_MUSIC_PB_CMD_PAUSE: + music.handler->pause(); + break; + case CELL_MUSIC_PB_CMD_NEXT: + music.handler->play(path); + break; + case CELL_MUSIC_PB_CMD_PREV: + music.handler->play(path); + break; + case CELL_MUSIC_PB_CMD_FASTFORWARD: + music.handler->fast_forward(); + break; + case CELL_MUSIC_PB_CMD_FASTREVERSE: + music.handler->fast_reverse(); + break; + default: + break; + } + music.func(ppu, CELL_MUSIC_EVENT_SET_PLAYBACK_COMMAND_RESULT, vm::addr_t(CELL_OK), music.userData); return CELL_OK; }); @@ -430,6 +557,8 @@ error_code cellMusicSetVolume(f32 level) if (!music.func) return CELL_MUSIC_ERROR_GENERIC; + music.handler->set_volume(level); + sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32 { music.func(ppu, CELL_MUSIC_EVENT_SET_VOLUME_RESULT, vm::addr_t(CELL_OK), music.userData); @@ -446,6 +575,9 @@ error_code cellMusicGetVolume2(vm::ptr level) if (!level) return CELL_MUSIC2_ERROR_PARAM; + const auto& music = g_fxo->get(); + *level = music.handler->get_volume(); + return CELL_OK; } diff --git a/rpcs3/Emu/Cell/Modules/cellMusic.h b/rpcs3/Emu/Cell/Modules/cellMusic.h index 828b4be4e1..cc2a09cb2b 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusic.h +++ b/rpcs3/Emu/Cell/Modules/cellMusic.h @@ -1,6 +1,8 @@ #pragma once #include "Emu/Memory/vm_ptr.h" +#include "Emu/Cell/ErrorCodes.h" +#include "cellSearch.h" // Error Codes enum CellMusicError : u32 @@ -132,5 +134,65 @@ using CellMusic2Callback = void(u32 event, vm::ptr param, vm::ptr us struct CellMusicSelectionContext { + // TODO: find out what fw does with it + // Let's just hijack it with our own data. char data[CELL_MUSIC_SELECTION_CONTEXT_SIZE]; }; + +struct music_selection_context +{ + char magic[4] = "SUS"; + u32 content_type{0}; + u32 repeat_mode{0}; + u32 context_option{0}; + std::string path; + + static constexpr u32 max_depth = 2; // root + 1 folder + file + + bool set(const CellMusicSelectionContext& in) + { + if (memcmp(in.data, magic, sizeof(magic)) != 0) + { + return false; + } + + u32 pos = sizeof(magic); + memcpy(&content_type, &in.data[pos], sizeof(content_type)); + pos += sizeof(content_type); + repeat_mode = in.data[pos++]; + context_option = in.data[pos++]; + path = &in.data[pos]; + + return true; + } + + CellMusicSelectionContext get() const + { + if (path.size() + 2 + sizeof(content_type) + sizeof(magic) > CELL_MUSIC_SELECTION_CONTEXT_SIZE) + { + fmt::throw_exception("Contents of music_selection_context are too large"); + } + + CellMusicSelectionContext out{}; + u32 pos = 0; + + std::memset(out.data, 0, CELL_MUSIC_SELECTION_CONTEXT_SIZE); + std::memcpy(out.data, magic, sizeof(magic)); + pos += sizeof(magic); + std::memcpy(&out.data[pos], &content_type, sizeof(content_type)); + pos += sizeof(content_type); + out.data[pos++] = repeat_mode; + out.data[pos++] = context_option; + std::memcpy(&out.data[pos], path.c_str(), path.size()); + + return out; + } + + std::string to_string() const + { + return fmt::format("{ .magic='%s', .content_type=%d, .repeat_mode=%d, .context_option=%d, .path='%s' }", magic, content_type, repeat_mode, context_option, path); + } + + // Helper + error_code find_content_id(vm::ptr contents_id); +}; diff --git a/rpcs3/Emu/Cell/Modules/cellSearch.cpp b/rpcs3/Emu/Cell/Modules/cellSearch.cpp index 7829af8997..b85cb7076a 100644 --- a/rpcs3/Emu/Cell/Modules/cellSearch.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSearch.cpp @@ -566,7 +566,7 @@ error_code cellSearchStartListSearch(CellSearchListSearchType type, CellSearchSo cellSearch.warning("cellSearchStartListSearch(): Directory-Path \"%s\" is too long and will be omitted: %i", item_path, item_path.length()); continue; // const size_t ext_offset = item.name.find_last_of('.'); - // const std::string link = "/.tmp/" + std::to_string(hash) + item.name.substr(ext_offset); + // std::string link = "/.tmp/" + std::to_string(hash) + item.name.substr(ext_offset); // strcpy_trunc(curr_find->infoPath.contentPath, link); // std::lock_guard lock(search.links_mutex); @@ -981,7 +981,7 @@ error_code cellSearchStartContentSearch(CellSearchContentSearchType type, CellSe { // Create mapping which will be resolved to an actual hard link in VFS by cellSearchPrepareFile const size_t ext_offset = item.name.find_last_of('.'); - const std::string link = "/.tmp/" + std::to_string(hash) + item.name.substr(ext_offset); + std::string link = "/.tmp/" + std::to_string(hash) + item.name.substr(ext_offset); strcpy_trunc(curr_find->infoPath.contentPath, link); std::lock_guard lock(search.links_mutex); @@ -1446,8 +1446,10 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrget(); + // TODO: find out if this check is correct - if (error_code error = check_search_state(g_fxo->get().state.load(), search_state::in_progress)) + if (error_code error = check_search_state(search.state.load(), search_state::in_progress)) { return error; } @@ -1457,6 +1459,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrcontent_ids[0]; const auto& first_content = first_content_id.second; @@ -1481,9 +1485,9 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrsecond->type, +first_content->type) }; } - // TODO: Use the found content - // first track/playlist of context = content->second; - cellSearch.todo("cellSearchGetMusicSelectionContext(): Hash=%08X, Assign found track: Type=0x%x, Path=%s", content_hash, +content->second->type, content->second->infoPath.contentPath); + // Use the found content + context.path = content->second->infoPath.contentPath; + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path=%s", content_hash, +content->second->type, context.path); } else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) { @@ -1492,9 +1496,9 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrtype, first_content->infoPath.contentPath); + // Select the first track by default + context.path = first_content->infoPath.contentPath; + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path=%s", content_hash, +first_content->type, context.path); } } else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) @@ -1504,13 +1508,24 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrtype, first_content->infoPath.contentPath); + // Select the first track by default + context.path = first_content->infoPath.contentPath; + cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.path); } - // TODO: Use repeatMode and option in our context, depending on the type (list vs single) - // TODO: assign outContext + context.content_type = first_content->type; + context.repeat_mode = repeatMode; + context.context_option = option; + + // Resolve hashed paths + if (auto found = search.content_links.find(context.path); found != search.content_links.end()) + { + context.path = found->second; + } + + *outContext = context.get(); + + cellSearch.success("cellSearchGetMusicSelectionContext: found selection context: %d", context.to_string()); return CELL_OK; } @@ -1544,7 +1559,12 @@ error_code cellSearchGetMusicSelectionContextOfSingleTrack(vm::cptrinfoPath.contentPath; + + *outContext = context.get(); + + cellSearch.success("cellSearchGetMusicSelectionContextOfSingleTrack: found selection context: %s", context.to_string()); return CELL_OK; } @@ -1576,7 +1596,7 @@ error_code cellSearchGetContentInfoPath(vm::cptr contentId, return CELL_SEARCH_ERROR_CONTENT_NOT_FOUND; } - cellSearch.success("contentId = %08X contentPath = \"%s\"", id, infoPath->contentPath); + cellSearch.success("contentId=%08X, contentPath=\"%s\"", id, infoPath->contentPath); return CELL_OK; } @@ -1781,3 +1801,110 @@ DECLARE(ppu_module_manager::cellSearch)("cellSearchUtility", []() REG_FUNC(cellSearchUtility, cellSearchCancel); REG_FUNC(cellSearchUtility, cellSearchEnd); }); + +// Helper +error_code music_selection_context::find_content_id(vm::ptr contents_id) +{ + if (!contents_id) + return CELL_MUSIC_ERROR_PARAM; + + // Search for the content that matches our current selection + auto& content_map = g_fxo->get(); + const u64 hash = std::hash()(this->path); + auto found = content_map.map.find(hash); + if (found != content_map.map.end()) + { + // TODO: check if the content type is correct + const u128 content_id_128 = found->first; + std::memcpy(contents_id->data, &content_id_128, CELL_SEARCH_CONTENT_ID_SIZE); + cellSearch.warning("find_content_id: found existing content for %s (path control: '%s')", to_string(), found->second->infoPath.contentPath); + return CELL_OK; + } + + // Try to find the content manually + const std::string vpath = vfs::get("/dev_hdd0/music/"); + for (auto&& entry : fs::dir(vpath)) + { + entry.name = vfs::unescape(entry.name); + + if (!entry.is_directory || entry.name == "." || entry.name == "..") + { + continue; + } + + std::string dir_path = vpath + entry.name; + + if (content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) + { + const u64 dir_hash = std::hash()(dir_path); + + if (hash == dir_hash) + { + u32 num_of_items = 0; + for (auto&& file : fs::dir(dir_path)) + { + file.name = vfs::unescape(file.name); + + if (file.is_directory || file.name == "." || file.name == "..") + { + continue; + } + + num_of_items++; + } + + // TODO: check for actual content inside the directory + std::shared_ptr curr_find = std::make_shared(); + curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSICLIST; + strcpy_trunc(curr_find->infoPath.contentPath, entry.name); // only save dir name due to 63 character limit + CellSearchMusicListInfo& info = curr_find->data.music_list; + info.listType = CELL_SEARCH_LISTSEARCHTYPE_MUSIC_ALBUM; + info.numOfItems = num_of_items; + info.duration = 0; + strcpy_trunc(info.title, entry.name); + strcpy_trunc(info.artistName, "ARTIST NAME"); + content_map.map.emplace(dir_hash, curr_find); + const u128 content_id_128 = dir_hash; + std::memcpy(contents_id->data, &content_id_128, CELL_SEARCH_CONTENT_ID_SIZE); + cellSearch.warning("find_content_id: found music list %s (path control: '%s')", to_string(), dir_path); + return CELL_OK; + } + + continue; + } + + // Search the subfolders. We assume all music is located in a depth of 2 (max_depth, root + 1 folder + file). + for (auto&& item : fs::dir(dir_path)) + { + if (item.is_directory || item.name == "." || item.name == "..") + { + continue; + } + + const std::string file_path = dir_path + "/" + item.name; + const u64 file_hash = std::hash()(file_path); + + if (hash == file_hash) + { + const auto [success, mi] = utils::get_media_info(path, 1); // AVMEDIA_TYPE_AUDIO + if (!success) + { + continue; + } + std::shared_ptr curr_find = std::make_shared(); + curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSIC; + const std::string short_path = entry.name + "/" + item.name; // only save dir name and item name due to 63 character limit + strcpy_trunc(curr_find->infoPath.contentPath, short_path); + populate_music_info(curr_find->data.music, mi, item); + content_map.map.emplace(file_hash, curr_find); + const u128 content_id_128 = file_hash; + std::memcpy(contents_id->data, &content_id_128, CELL_SEARCH_CONTENT_ID_SIZE); + cellSearch.warning("find_content_id: found music track %s (path control: '%s')", to_string(), file_path); + return CELL_OK; + } + } + } + + // content ID not found + return CELL_SEARCH_ERROR_CONTENT_NOT_FOUND; +} diff --git a/rpcs3/Emu/Io/Null/null_music_handler.h b/rpcs3/Emu/Io/Null/null_music_handler.h new file mode 100644 index 0000000000..791ae7abe3 --- /dev/null +++ b/rpcs3/Emu/Io/Null/null_music_handler.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Emu/Io/music_handler_base.h" + +class null_music_handler final : public music_handler_base +{ +public: + null_music_handler() : music_handler_base() {} + + void stop() override { m_state = 0; } // CELL_MUSIC_PB_STATUS_STOP + void play(const std::string& /*path*/) override { m_state = 1; } // CELL_MUSIC_PB_STATUS_PLAY + void pause() override { m_state = 2; } // CELL_MUSIC_PB_STATUS_PAUSE + void fast_forward() override { m_state = 3; } // CELL_MUSIC_PB_STATUS_FASTFORWARD + void fast_reverse() override { m_state = 4; } // CELL_MUSIC_PB_STATUS_FASTREVERSE + void set_volume(f32 volume) override { m_volume = volume; } + f32 get_volume() const override { return m_volume; } + +private: + atomic_t m_volume{0.0f}; +}; diff --git a/rpcs3/Emu/Io/music_handler_base.h b/rpcs3/Emu/Io/music_handler_base.h new file mode 100644 index 0000000000..c3eaf65beb --- /dev/null +++ b/rpcs3/Emu/Io/music_handler_base.h @@ -0,0 +1,26 @@ +#pragma once + +#include "util/types.hpp" +#include "util/atomic.hpp" + +class music_handler_base +{ +public: + virtual ~music_handler_base() = default; + + virtual void stop() = 0; + virtual void play(const std::string& path) = 0; + virtual void pause() = 0; + virtual void fast_forward() = 0; + virtual void fast_reverse() = 0; + virtual void set_volume(f32 volume) = 0; + virtual f32 get_volume() const = 0; + + s32 get_state() const + { + return m_state; + } + +protected: + atomic_t m_state{0}; +}; diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index f3ad23332a..75cdb507b5 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -71,6 +71,7 @@ struct EmuCallbacks std::function()> get_gs_frame; std::function init_gs_render; std::function()> get_camera_handler; + std::function()> get_music_handler; std::function()> get_audio; std::function()> get_msg_dialog; std::function()> get_osk_dialog; diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 74bda4a38c..9c24b95eae 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -239,6 +239,7 @@ struct cfg_root : cfg::node cfg::_int<0, 100> time_stretching_threshold{ this, "Time Stretching Threshold", 75, true }; cfg::_enum microphone_type{ this, "Microphone Type", microphone_handler::null }; cfg::string microphone_devices{ this, "Microphone Devices", "@@@@@@@@@@@@" }; + cfg::_enum music{ this, "Music Handler", music_handler::qt }; } audio{ this }; struct node_io : cfg::node diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index 04bf5de235..cdafc4efc7 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -335,6 +335,21 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case music_handler::null: return "Null"; + case music_handler::qt: return "Qt"; + } + + return unknown; + }); +} + template <> void fmt_class_string::format(std::string& out, u64 arg) { diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index 6bd4db0316..ee78d0723f 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -75,6 +75,12 @@ enum class audio_downmix use_application_settings }; +enum class music_handler +{ + null, + qt +}; + enum class camera_handler { null, diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index a5cc8bbc05..5741025b0f 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -450,7 +450,9 @@ + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 1141ac48bc..9f79dc4f77 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -2041,6 +2041,12 @@ Emu\NP + + Emu\Io + + + Emu\Io\Null + diff --git a/rpcs3/headless_application.cpp b/rpcs3/headless_application.cpp index cafbe597b5..267d4e590c 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -6,6 +6,7 @@ #include "Emu/Cell/Modules/cellSaveData.h" #include "Emu/Cell/Modules/sceNpTrophy.h" #include "Emu/Io/Null/null_camera_handler.h" +#include "Emu/Io/Null/null_music_handler.h" #include @@ -105,6 +106,22 @@ void headless_application::InitializeCallbacks() return nullptr; }; + callbacks.get_music_handler = []() -> std::shared_ptr + { + switch (g_cfg.audio.music.get()) + { + case music_handler::null: + { + return std::make_shared(); + } + case music_handler::qt: + { + fmt::throw_exception("Headless mode can not be used with this music handler. Current handler: %s", g_cfg.audio.music.get()); + } + } + return nullptr; + }; + callbacks.get_gs_frame = []() -> std::unique_ptr { if (g_cfg.video.renderer != video_renderer::null) diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 525939a3eb..9bc8d027ae 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -309,6 +309,9 @@ true + + true + true @@ -513,6 +516,9 @@ true + + true + true @@ -609,6 +615,8 @@ + + @@ -1127,6 +1135,17 @@ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" + + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 04fdc6c4b5..dc31ace380 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -136,6 +136,9 @@ {f1996891-48b7-441a-b3fe-52794cbebe80} + + {44c3b8d8-cc4d-4d5f-8681-21241ab220d0} + @@ -807,6 +810,18 @@ Gui\utils + + Io\music + + + Io\music + + + Generated Files\Debug + + + Generated Files\Release + @@ -953,6 +968,9 @@ Gui\utils + + Io\music + @@ -1189,6 +1207,9 @@ Gui\settings + + Io\music + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index f900c39769..82fa2ebd27 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -53,6 +53,8 @@ set(SRC_FILES qt_camera_error_handler.cpp qt_camera_handler.cpp qt_camera_video_surface.cpp + qt_music_error_handler.cpp + qt_music_handler.cpp qt_utils.cpp register_editor_dialog.cpp recvmessage_dialog_frame.cpp diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 40c089dd54..06b6b7bd0b 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -1027,6 +1027,13 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case camera_handler::qt: return tr("Qt", "Camera handler"); } break; + case emu_settings_type::MusicHandler: + switch (static_cast(index)) + { + case music_handler::null: return tr("Null", "Music handler"); + case music_handler::qt: return tr("Qt", "Music handler"); + } + break; case emu_settings_type::PadHandlerMode: switch (static_cast(index)) { diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index c107ecad5b..512d45dd98 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -126,6 +126,7 @@ enum class emu_settings_type TimeStretchingThreshold, MicrophoneType, MicrophoneDevices, + MusicHandler, // Input / Output PadHandlerMode, @@ -290,6 +291,7 @@ inline static const QMap settings_location = { emu_settings_type::TimeStretchingThreshold, { "Audio", "Time Stretching Threshold"}}, { emu_settings_type::MicrophoneType, { "Audio", "Microphone Type" }}, { emu_settings_type::MicrophoneDevices, { "Audio", "Microphone Devices" }}, + { emu_settings_type::MusicHandler, { "Audio", "Music Handler"}}, // Input / Output { emu_settings_type::PadHandlerMode, { "Input/Output", "Pad handler mode"}}, diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index 854a0aed2d..a402cedc3c 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -11,12 +11,14 @@ #include "display_sleep_control.h" #include "localized_emu.h" #include "qt_camera_handler.h" +#include "qt_music_handler.h" #ifdef WITH_DISCORD_RPC #include "_discord_utils.h" #endif #include "Emu/Io/Null/null_camera_handler.h" +#include "Emu/Io/Null/null_music_handler.h" #include "Emu/Cell/Modules/cellAudio.h" #include "Emu/RSX/Overlays/overlay_perf_metrics.h" #include "Emu/system_utils.hpp" @@ -379,6 +381,23 @@ void gui_application::InitializeCallbacks() } return nullptr; }; + + callbacks.get_music_handler = []() -> std::shared_ptr + { + switch (g_cfg.audio.music.get()) + { + case music_handler::null: + { + return std::make_shared(); + } + case music_handler::qt: + { + return std::make_shared(); + } + } + return nullptr; + }; + callbacks.get_gs_frame = [this]() -> std::unique_ptr { return get_gs_frame(); }; callbacks.get_msg_dialog = [this]() -> std::shared_ptr { return m_show_gui ? std::make_shared() : nullptr; }; callbacks.get_osk_dialog = [this]() -> std::shared_ptr { return m_show_gui ? std::make_shared() : nullptr; }; diff --git a/rpcs3/rpcs3qt/qt_music_error_handler.cpp b/rpcs3/rpcs3qt/qt_music_error_handler.cpp new file mode 100644 index 0000000000..305d1b531f --- /dev/null +++ b/rpcs3/rpcs3qt/qt_music_error_handler.cpp @@ -0,0 +1,92 @@ +#include "qt_music_error_handler.h" +#include "util/logs.hpp" + +LOG_CHANNEL(music_log, "Music"); + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](QMediaPlayer::Error value) + { + switch (value) + { + case QMediaPlayer::Error::NoError: return "NoError"; + case QMediaPlayer::Error::ResourceError: return "ResourceError"; + case QMediaPlayer::Error::FormatError: return "FormatError"; + case QMediaPlayer::Error::NetworkError: return "NetworkError"; + case QMediaPlayer::Error::AccessDeniedError: return "AccessDeniedError"; + case QMediaPlayer::Error::ServiceMissingError: return "ServiceMissingError"; + case QMediaPlayer::Error::MediaIsPlaylist: return "MediaIsPlaylist"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](QMediaPlayer::MediaStatus value) + { + switch (value) + { + case QMediaPlayer::MediaStatus::UnknownMediaStatus: return "UnknownMediaStatus"; + case QMediaPlayer::MediaStatus::NoMedia: return "NoMedia"; + case QMediaPlayer::MediaStatus::LoadingMedia: return "LoadingMedia"; + case QMediaPlayer::MediaStatus::LoadedMedia: return "LoadedMedia"; + case QMediaPlayer::MediaStatus::StalledMedia: return "StalledMedia"; + case QMediaPlayer::MediaStatus::BufferingMedia: return "BufferingMedia"; + case QMediaPlayer::MediaStatus::BufferedMedia: return "BufferedMedia"; + case QMediaPlayer::MediaStatus::EndOfMedia: return "EndOfMedia"; + case QMediaPlayer::MediaStatus::InvalidMedia: return "InvalidMedia"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](QMediaPlayer::State value) + { + switch (value) + { + case QMediaPlayer::State::StoppedState: return "StoppedState"; + case QMediaPlayer::State::PlayingState: return "PlayingState"; + case QMediaPlayer::State::PausedState: return "PausedState"; + } + + return unknown; + }); +} + +qt_music_error_handler::qt_music_error_handler(std::shared_ptr media_player) + : m_media_player(std::move(media_player)) +{ + if (m_media_player) + { + connect(m_media_player.get(), &QMediaPlayer::mediaStatusChanged, this, &qt_music_error_handler::handle_media_status); + connect(m_media_player.get(), &QMediaPlayer::stateChanged, this, &qt_music_error_handler::handle_music_state); + connect(m_media_player.get(), QOverload::of(&QMediaPlayer::error), this, &qt_music_error_handler::handle_music_error); + } +} + +qt_music_error_handler::~qt_music_error_handler() +{ +} + +void qt_music_error_handler::handle_media_status(QMediaPlayer::MediaStatus status) +{ + music_log.notice("New media status: %s (status=%d)", status, static_cast(status)); +} + +void qt_music_error_handler::handle_music_state(QMediaPlayer::State state) +{ + music_log.notice("New playback state: %s (state=%d)", state, static_cast(state)); +} + +void qt_music_error_handler::handle_music_error(QMediaPlayer::Error error) +{ + music_log.error("Error event: \"%s\" (error=%s)", m_media_player ? m_media_player->errorString().toStdString() : "", error); +} diff --git a/rpcs3/rpcs3qt/qt_music_error_handler.h b/rpcs3/rpcs3qt/qt_music_error_handler.h new file mode 100644 index 0000000000..d71a85d639 --- /dev/null +++ b/rpcs3/rpcs3qt/qt_music_error_handler.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class qt_music_error_handler : public QObject +{ + Q_OBJECT + +public: + qt_music_error_handler(std::shared_ptr media_player); + virtual ~qt_music_error_handler(); + +private Q_SLOTS: + void handle_media_status(QMediaPlayer::MediaStatus status); + void handle_music_state(QMediaPlayer::State state); + void handle_music_error(QMediaPlayer::Error error); + +private: + std::shared_ptr m_media_player; +}; diff --git a/rpcs3/rpcs3qt/qt_music_handler.cpp b/rpcs3/rpcs3qt/qt_music_handler.cpp new file mode 100644 index 0000000000..eae2ca57c6 --- /dev/null +++ b/rpcs3/rpcs3qt/qt_music_handler.cpp @@ -0,0 +1,185 @@ +#include "qt_music_handler.h" +#include "Emu/Cell/Modules/cellMusic.h" +#include "Emu/System.h" +#include "Utilities/Thread.h" +#include "util/logs.hpp" + +#include + +LOG_CHANNEL(music_log, "Music"); + +qt_music_handler::qt_music_handler() +{ + music_log.notice("Constructing Qt music handler..."); + m_media_player = std::make_shared(); + m_error_handler = std::make_unique(m_media_player); +} + +qt_music_handler::~qt_music_handler() +{ + atomic_t wake_up = false; + + Emu.CallFromMainThread([&wake_up, this]() + { + music_log.notice("Destroying Qt music handler..."); + m_media_player->stop(); + m_media_player.reset(); + m_error_handler.reset(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } +} + +void qt_music_handler::play(const std::string& path) +{ + std::lock_guard lock(m_mutex); + atomic_t wake_up = false; + + Emu.CallFromMainThread([&wake_up, &path, this]() + { + music_log.notice("Playing music: %s", path); + m_media_player->setAudioRole(QAudio::Role::MusicRole); + m_media_player->setPlaybackRate(1.0); + m_media_player->setMedia(QUrl(QString::fromStdString(path))); + m_media_player->play(); + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } + + m_state = CELL_MUSIC_PB_STATUS_PLAY; +} + +void qt_music_handler::stop() +{ + std::lock_guard lock(m_mutex); + atomic_t wake_up = false; + + Emu.CallFromMainThread([&wake_up, this]() + { + music_log.notice("Stopping music..."); + m_media_player->stop(); + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } + + m_state = CELL_MUSIC_PB_STATUS_STOP; +} + +void qt_music_handler::pause() +{ + std::lock_guard lock(m_mutex); + atomic_t wake_up = false; + + Emu.CallFromMainThread([&wake_up, this]() + { + music_log.notice("Pausing music..."); + m_media_player->pause(); + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } + + m_state = CELL_MUSIC_PB_STATUS_PAUSE; +} + +void qt_music_handler::fast_forward() +{ + std::lock_guard lock(m_mutex); + atomic_t wake_up = false; + + Emu.CallFromMainThread([&wake_up, this]() + { + music_log.notice("Fast-forwarding music..."); + m_media_player->setPlaybackRate(2.0); + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } + + m_state = CELL_MUSIC_PB_STATUS_FASTFORWARD; +} + +void qt_music_handler::fast_reverse() +{ + std::lock_guard lock(m_mutex); + atomic_t wake_up = false; + + Emu.CallFromMainThread([&wake_up, this]() + { + music_log.notice("Fast-reversing music..."); + m_media_player->setPlaybackRate(-2.0); + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } + + m_state = CELL_MUSIC_PB_STATUS_FASTREVERSE; +} + +void qt_music_handler::set_volume(f32 volume) +{ + std::lock_guard lock(m_mutex); + atomic_t wake_up = false; + + Emu.CallFromMainThread([&wake_up, &volume, this]() + { + const int new_volume = std::max(0, std::min(volume * 100, 100)); + music_log.notice("Setting volume to %d%%", new_volume); + m_media_player->setVolume(new_volume); + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } +} + +f32 qt_music_handler::get_volume() const +{ + std::lock_guard lock(m_mutex); + atomic_t wake_up = false; + f32 volume = 0.0f; + + Emu.CallFromMainThread([&wake_up, &volume, this]() + { + const int current_volume = std::max(0, std::min(m_media_player->volume(), 100)); + music_log.notice("Getting volume: %d%%", current_volume); + volume = current_volume / 100.0f; + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } + + return volume; +} diff --git a/rpcs3/rpcs3qt/qt_music_handler.h b/rpcs3/rpcs3qt/qt_music_handler.h new file mode 100644 index 0000000000..83793383ec --- /dev/null +++ b/rpcs3/rpcs3qt/qt_music_handler.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Emu/Io/music_handler_base.h" +#include "qt_music_error_handler.h" + +#include +#include + +class qt_music_handler final : public music_handler_base +{ +public: + qt_music_handler(); + virtual ~qt_music_handler(); + + void stop() override; + void play(const std::string& path) override; + void pause() override; + void fast_forward() override; + void fast_reverse() override; + void set_volume(f32 volume) override; + f32 get_volume() const override; + +private: + mutable std::mutex m_mutex; + std::unique_ptr m_error_handler; + std::shared_ptr m_media_player; +}; diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index edd436cae6..adbfe6e360 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1063,6 +1063,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std SubscribeTooltip(ui->gb_camera_id, tooltips.settings.camera_id); } + m_emu_settings->EnhanceComboBox(ui->musicHandlerBox, emu_settings_type::MusicHandler); + SubscribeTooltip(ui->gb_music_handler, tooltips.settings.music_handler); + m_emu_settings->EnhanceComboBox(ui->padModeBox, emu_settings_type::PadHandlerMode); SubscribeTooltip(ui->gb_pad_mode, tooltips.settings.pad_mode); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index ffc0730a59..86adb12585 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -43,8 +43,8 @@ 0 0 - 821 - 667 + 838 + 641 @@ -54,7 +54,7 @@ - 6 + 2 @@ -1077,6 +1077,34 @@ + + + + Music Handler + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 0 + 0 + + + + @@ -1244,6 +1272,22 @@ + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 0 + 0 + + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index f011cb2d61..9b5dc346a0 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -198,6 +198,7 @@ public: const QString pad_mode = tr("Single-threaded: All pad handlers run on the same thread sequentially.\nMulti-threaded: Each pad handler has its own thread.\nOnly use multi-threaded if you can spare the extra threads."); const QString keyboard_handler = tr("Some games support native keyboard input.\nBasic will work in these cases."); const QString mouse_handler = tr("Some games support native mouse input.\nBasic will work in these cases."); + const QString music_handler = tr("Currently only used for cellMusic emulation.\nSelect Qt to use the default output device of your operating system.\nThis may not be able to play all audio formats."); const QString camera = tr("Select Qt Camera to use the default camera device of your operating system."); const QString camera_type = tr("Depending on the game, you may need to select a specific camera type."); const QString camera_flip = tr("Flips the camera image either horizontally, vertically, or on both axis."); diff --git a/rpcs3/util/media_utils.cpp b/rpcs3/util/media_utils.cpp index 324352e054..888ba990f3 100644 --- a/rpcs3/util/media_utils.cpp +++ b/rpcs3/util/media_utils.cpp @@ -59,6 +59,7 @@ namespace utils std::pair get_media_info(const std::string& path, s32 av_media_type) { media_info info; + info.path = path; // Only print FFMPEG errors, fatals and panics av_log_set_level(AV_LOG_ERROR); diff --git a/rpcs3/util/media_utils.h b/rpcs3/util/media_utils.h index 1917d0dc5a..5635812565 100644 --- a/rpcs3/util/media_utils.h +++ b/rpcs3/util/media_utils.h @@ -6,6 +6,8 @@ namespace utils { struct media_info { + std::string path; + s32 audio_av_codec_id = 0; // 0 = AV_CODEC_ID_NONE s32 video_av_codec_id = 0; // 0 = AV_CODEC_ID_NONE s32 audio_bitrate_bps = 0; // Bit rate in bit/s