From aafd74f9eab4e96c68f2a740350d3b196f85e8ef Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 5 Mar 2022 14:20:07 +0100 Subject: [PATCH] cellMusicDecode: initial implementation Implements the basic functionality of cellMusicDecode. Works with Space Invaders (if you add the list selection from the other PR). Probably fixes SSX custom music. --- 3rdparty/CMakeLists.txt | 6 +- buildfiles/cmake/FindFFMPEG.cmake | 14 +- rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp | 310 +++++++++++++++--- .../RSX/Overlays/overlay_user_list_dialog.cpp | 2 +- rpcs3/util/media_utils.cpp | 259 +++++++++++++++ rpcs3/util/media_utils.h | 31 ++ 6 files changed, 569 insertions(+), 53 deletions(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 232616e487..8497c2c0e2 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -242,6 +242,7 @@ else() set(FFMPEG_LIB_AVCODEC "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg/windows/x86_64/lib/libavcodec.a") set(FFMPEG_LIB_AVUTIL "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg/windows/x86_64/lib/libavutil.a") set(FFMPEG_LIB_SWSCALE "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg/windows/x86_64/lib/libswscale.a") + set(FFMPEG_LIB_SWRESAMPLE "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg/windows/x86_64/lib/libswresample.a") else() message("-- RPCS3: using builtin ffmpeg") @@ -260,6 +261,7 @@ else() find_library(FFMPEG_LIB_AVCODEC avcodec PATHS ${FFMPEG_LIB_DIR} NO_DEFAULT_PATH) find_library(FFMPEG_LIB_AVUTIL avutil PATHS ${FFMPEG_LIB_DIR} NO_DEFAULT_PATH) find_library(FFMPEG_LIB_SWSCALE swscale PATHS ${FFMPEG_LIB_DIR} NO_DEFAULT_PATH) + find_library(FFMPEG_LIB_SWRESAMPLE swresample PATHS ${FFMPEG_LIB_DIR} NO_DEFAULT_PATH) endif() target_include_directories(3rdparty_ffmpeg INTERFACE "ffmpeg/include") @@ -269,7 +271,9 @@ else() ${FFMPEG_LIB_AVFORMAT} ${FFMPEG_LIB_AVCODEC} ${FFMPEG_LIB_AVUTIL} - ${FFMPEG_LIB_SWSCALE}) + ${FFMPEG_LIB_SWSCALE} + ${FFMPEG_LIB_SWRESAMPLE} + ) endif() diff --git a/buildfiles/cmake/FindFFMPEG.cmake b/buildfiles/cmake/FindFFMPEG.cmake index 510f53fa58..79144eb94c 100644 --- a/buildfiles/cmake/FindFFMPEG.cmake +++ b/buildfiles/cmake/FindFFMPEG.cmake @@ -1,4 +1,4 @@ -# - Try to find ffmpeg libraries (libavcodec, libavformat, libavutil and libswscale) +# - Try to find ffmpeg libraries (libavcodec, libavformat, libavutil, libswresample and libswscale) # Once done this will define # # FFMPEG_FOUND - system has ffmpeg or libav @@ -8,6 +8,7 @@ # FFMPEG_LIBAVFORMAT # FFMPEG_LIBAVUTIL # FFMPEG_LIBSWSCALE +# FFMPEG_LIBSWRESAMPLE # # Copyright (c) 2008 Andreas Schneider # Modified for other libraries by Lasse Kärkkäinen @@ -29,6 +30,7 @@ else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) pkg_check_modules(_FFMPEG_AVFORMAT libavformat) pkg_check_modules(_FFMPEG_AVUTIL libavutil) pkg_check_modules(_FFMPEG_SWSCALE libswscale) + pkg_check_modules(_FFMPEG_SWRESAMPLE libswresample) endif (PKG_CONFIG_FOUND) find_path(FFMPEG_AVCODEC_INCLUDE_DIR @@ -57,7 +59,12 @@ else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) PATHS ${_FFMPEG_SWSCALE_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib ) - if (FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVFORMAT AND FFMPEG_LIBSWSCALE) + find_library(FFMPEG_LIBSWRESAMPLE + NAMES swresample + PATHS ${_FFMPEG_SWRESAMPLE_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib + ) + + if (FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVFORMAT AND FFMPEG_LIBSWSCALE AND FFMPEG_LIBSWRESAMPLE) set(FFMPEG_FOUND TRUE) endif() @@ -69,6 +76,7 @@ else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) ${FFMPEG_LIBAVFORMAT} ${FFMPEG_LIBAVUTIL} ${FFMPEG_LIBSWSCALE} + ${FFMPEG_LIBSWRESAMPLE} ) endif (FFMPEG_FOUND) @@ -79,7 +87,7 @@ else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) endif (NOT FFMPEG_FIND_QUIETLY) else (FFMPEG_FOUND) if (FFMPEG_FIND_REQUIRED) - message(FATAL_ERROR "Could not find libavcodec or libavformat or libavutil or libswscale") + message(FATAL_ERROR "Could not find libavcodec or libavformat or libavutil or libswscale or libswresample") endif (FFMPEG_FIND_REQUIRED) endif (FFMPEG_FOUND) diff --git a/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp b/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp index dcf9ed58c9..ff09d02e02 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusicDecode.cpp @@ -3,18 +3,22 @@ #include "Emu/Cell/lv2/sys_lwmutex.h" #include "Emu/Cell/lv2/sys_lwcond.h" #include "Emu/Cell/lv2/sys_spu.h" +#include "Emu/VFS.h" #include "cellMusic.h" #include "cellSearch.h" #include "cellSpurs.h" #include "cellSysutil.h" +#include "util/media_utils.h" +#include LOG_CHANNEL(cellMusicDecode); -// Return Codes +// Return Codes (CELL_MUSIC_DECODE2 codes are omitted if they are identical) enum CellMusicDecodeError : u32 { + CELL_MUSIC_DECODE_CANCELED = 1, CELL_MUSIC_DECODE_DECODE_FINISHED = 0x8002C101, CELL_MUSIC_DECODE_ERROR_PARAM = 0x8002C102, CELL_MUSIC_DECODE_ERROR_BUSY = 0x8002C103, @@ -37,6 +41,7 @@ void fmt_class_string::format(std::string& out, u64 arg) { switch (error) { + STR_CASE(CELL_MUSIC_DECODE_CANCELED); STR_CASE(CELL_MUSIC_DECODE_DECODE_FINISHED); STR_CASE(CELL_MUSIC_DECODE_ERROR_PARAM); STR_CASE(CELL_MUSIC_DECODE_ERROR_BUSY); @@ -56,6 +61,7 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +// Constants (CELL_MUSIC_DECODE2 codes are omitted if they are identical) enum { CELL_MUSIC_DECODE_EVENT_STATUS_NOTIFICATION = 0, @@ -66,18 +72,37 @@ enum CELL_MUSIC_DECODE_EVENT_SET_SELECTION_CONTEXT_RESULT = 5, CELL_MUSIC_DECODE_EVENT_UI_NOTIFICATION = 6, CELL_MUSIC_DECODE_EVENT_NEXT_CONTENTS_READY_RESULT = 7, -}; -enum -{ - CELL_MUSIC_DECODE2_EVENT_STATUS_NOTIFICATION = 0, - CELL_MUSIC_DECODE2_EVENT_INITIALIZE_RESULT = 1, - CELL_MUSIC_DECODE2_EVENT_FINALIZE_RESULT = 2, - CELL_MUSIC_DECODE2_EVENT_SELECT_CONTENTS_RESULT = 3, - CELL_MUSIC_DECODE2_EVENT_SET_DECODE_COMMAND_RESULT = 4, - CELL_MUSIC_DECODE2_EVENT_SET_SELECTION_CONTEXT_RESULT = 5, - CELL_MUSIC_DECODE2_EVENT_UI_NOTIFICATION = 6, - CELL_MUSIC_DECODE2_EVENT_NEXT_CONTENTS_READY_RESULT = 7, + CELL_MUSIC_DECODE_MODE_NORMAL = 0, + + CELL_MUSIC_DECODE_CMD_STOP = 0, + CELL_MUSIC_DECODE_CMD_START = 1, + CELL_MUSIC_DECODE_CMD_NEXT = 2, + CELL_MUSIC_DECODE_CMD_PREV = 3, + + CELL_MUSIC_DECODE_STATUS_DORMANT = 0, + CELL_MUSIC_DECODE_STATUS_DECODING = 1, + + CELL_MUSIC_DECODE_POSITION_NONE = 0, + CELL_MUSIC_DECODE_POSITION_START = 1, + CELL_MUSIC_DECODE_POSITION_MID = 2, + CELL_MUSIC_DECODE_POSITION_END = 3, + CELL_MUSIC_DECODE_POSITION_END_LIST_END = 4, + + CELL_MUSIC_DECODE2_SPEED_MAX = 0, + CELL_MUSIC_DECODE2_SPEED_2 = 2, + + CELL_SYSUTIL_MUSIC_DECODE2_INITIALIZING_FINISHED = 1, + CELL_SYSUTIL_MUSIC_DECODE2_SHUTDOWN_FINISHED = 4, // 3(SDK103) -> 4(SDK110) + CELL_SYSUTIL_MUSIC_DECODE2_LOADING_FINISHED = 5, + CELL_SYSUTIL_MUSIC_DECODE2_UNLOADING_FINISHED = 7, + CELL_SYSUTIL_MUSIC_DECODE2_RELEASED = 9, + CELL_SYSUTIL_MUSIC_DECODE2_GRABBED = 11, + + CELL_MUSIC_DECODE2_MIN_BUFFER_SIZE = 448 * 1024, + CELL_MUSIC_DECODE2_MANAGEMENT_SIZE = 64 * 1024, + CELL_MUSIC_DECODE2_PAGESIZE_64K = 64 * 1024, + CELL_MUSIC_DECODE2_PAGESIZE_1M = 1 * 1024 * 1024, }; using CellMusicDecodeCallback = void(u32, vm::ptr param, vm::ptr userData); @@ -87,29 +112,131 @@ struct music_decode { vm::ptr func{}; vm::ptr userData{}; + music_selection_context current_selection_context{}; + s32 decode_status = CELL_MUSIC_DECODE_STATUS_DORMANT; + s32 decode_command = CELL_MUSIC_DECODE_CMD_STOP; + u64 readPos = 0; + utils::audio_decoder decoder{}; shared_mutex mutex; + + error_code set_decode_command(s32 command) + { + decode_command = command; + + switch (command) + { + case CELL_MUSIC_DECODE_CMD_STOP: + { + decoder.stop(); + decode_status = CELL_MUSIC_DECODE_STATUS_DORMANT; + break; + } + case CELL_MUSIC_DECODE_CMD_START: + { + decode_status = CELL_MUSIC_DECODE_STATUS_DECODING; + readPos = 0; + + // Decode data. The format of the decoded data is 48kHz, float 32bit, 2ch LPCM data interleaved in order from left to right. + decoder.set_path(vfs::get(current_selection_context.path)); + decoder.set_swap_endianness(true); + decoder.decode(); + break; + } + case CELL_MUSIC_DECODE_CMD_NEXT: // TODO: set path of next file if possible + case CELL_MUSIC_DECODE_CMD_PREV: // TODO: set path of prev file if possible + { + return CELL_MUSIC_DECODE_ERROR_NO_MORE_CONTENT; + } + } + return CELL_OK; + } + + error_code finalize() + { + decoder.stop(); + decode_status = CELL_MUSIC_DECODE_STATUS_DORMANT; + decode_command = CELL_MUSIC_DECODE_CMD_STOP; + readPos = 0; + return CELL_OK; + } }; -struct music_decode2 +struct music_decode2 : music_decode { - vm::ptr func{}; - vm::ptr userData{}; - - shared_mutex mutex; + s32 speed = CELL_MUSIC_DECODE2_SPEED_MAX; }; +template +error_code cell_music_decode_read(vm::ptr buf, vm::ptr startTime, u64 reqSize, vm::ptr readSize, vm::ptr position) +{ + if (!buf || !startTime || !position || !readSize) + return CELL_MUSIC_DECODE_ERROR_PARAM; + + auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); + std::scoped_lock slock(dec.decoder.m_mtx); + + if (dec.decoder.has_error) + return CELL_MUSIC_DECODE_ERROR_DECODE_FAILURE; + + if (dec.decoder.m_size == 0) + { + *position = CELL_MUSIC_DECODE_POSITION_NONE; + *readSize = 0; + return CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA; + } + + if (dec.readPos == 0) + { + *position = CELL_MUSIC_DECODE_POSITION_START; + } + else if ((dec.readPos + reqSize) >= dec.decoder.m_size) + { + *position = CELL_MUSIC_DECODE_POSITION_END_LIST_END; + } + else + { + *position = CELL_MUSIC_DECODE_POSITION_MID; + } + + const u64 size_to_read = (dec.readPos + reqSize) <= dec.decoder.m_size ? reqSize : dec.decoder.m_size - dec.readPos; + std::memcpy(buf.get_ptr(), &dec.decoder.data[dec.readPos], size_to_read); + + dec.readPos += size_to_read; + *readSize = size_to_read; + + s64 start_time_ms = 0; + + if (!dec.decoder.timestamps_ms.empty()) + { + start_time_ms = dec.decoder.timestamps_ms.front().second; + + while (dec.decoder.timestamps_ms.size() > 1 && dec.readPos >= dec.decoder.timestamps_ms.at(1).first) + { + dec.decoder.timestamps_ms.pop_front(); + } + } + + *startTime = static_cast(start_time_ms); // startTime is milliseconds + + cellMusicDecode.trace("cell_music_decode_read(size_to_read=%d, samples=%d, start_time_ms=%d)", size_to_read, size_to_read / sizeof(u64), start_time_ms); + + return CELL_OK; +} + error_code cellMusicDecodeInitialize(s32 mode, u32 container, s32 spuPriority, vm::ptr func, vm::ptr userData) { cellMusicDecode.todo("cellMusicDecodeInitialize(mode=0x%x, container=0x%x, spuPriority=0x%x, func=*0x%x, userData=*0x%x)", mode, container, spuPriority, func, userData); auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); dec.func = func; dec.userData = userData; - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 + sysutil_register_cb([&dec](ppu_thread& ppu) -> s32 { - dec.func(ppu, CELL_MUSIC_DECODE_EVENT_INITIALIZE_RESULT, vm::addr_t(CELL_OK), userData); + dec.func(ppu, CELL_MUSIC_DECODE_EVENT_INITIALIZE_RESULT, vm::addr_t(CELL_OK), dec.userData); return CELL_OK; }); @@ -121,12 +248,13 @@ error_code cellMusicDecodeInitializeSystemWorkload(s32 mode, u32 container, vm:: cellMusicDecode.todo("cellMusicDecodeInitializeSystemWorkload(mode=0x%x, container=0x%x, func=*0x%x, userData=*0x%x, spuUsageRate=0x%x, spurs=*0x%x, priority=*0x%x, attr=*0x%x)", mode, container, func, userData, spuUsageRate, spurs, priority, attr); auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); dec.func = func; dec.userData = userData; - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 + sysutil_register_cb([&dec](ppu_thread& ppu) -> s32 { - dec.func(ppu, CELL_MUSIC_DECODE_EVENT_INITIALIZE_RESULT, vm::addr_t(CELL_OK), userData); + dec.func(ppu, CELL_MUSIC_DECODE_EVENT_INITIALIZE_RESULT, vm::addr_t(CELL_OK), dec.userData); return CELL_OK; }); @@ -138,10 +266,12 @@ error_code cellMusicDecodeFinalize() cellMusicDecode.todo("cellMusicDecodeFinalize()"); auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); + dec.finalize(); if (dec.func) { - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 + sysutil_register_cb([&dec](ppu_thread& ppu) -> s32 { dec.func(ppu, CELL_MUSIC_DECODE_EVENT_FINALIZE_RESULT, vm::addr_t(CELL_OK), dec.userData); return CELL_OK; @@ -156,6 +286,7 @@ error_code cellMusicDecodeSelectContents() cellMusicDecode.todo("cellMusicDecodeSelectContents()"); auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); if (!dec.func) return CELL_MUSIC_DECODE_ERROR_GENERIC; @@ -174,13 +305,16 @@ error_code cellMusicDecodeSetDecodeCommand(s32 command) cellMusicDecode.todo("cellMusicDecodeSetDecodeCommand(command=0x%x)", command); auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); if (!dec.func) return CELL_MUSIC_DECODE_ERROR_GENERIC; - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 + const error_code result = dec.set_decode_command(command); + + sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32 { - dec.func(ppu, CELL_MUSIC_DECODE_EVENT_SET_DECODE_COMMAND_RESULT, vm::addr_t(CELL_OK), dec.userData); + dec.func(ppu, CELL_MUSIC_DECODE_EVENT_SET_DECODE_COMMAND_RESULT, vm::addr_t(s32{result}), dec.userData); return CELL_OK; }); @@ -190,18 +324,36 @@ error_code cellMusicDecodeSetDecodeCommand(s32 command) error_code cellMusicDecodeGetDecodeStatus(vm::ptr status) { cellMusicDecode.todo("cellMusicDecodeGetDecodeStatus(status=*0x%x)", status); + + if (!status) + return CELL_MUSIC_DECODE_ERROR_PARAM; + + auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); + *status = dec.decode_status; + return CELL_OK; } error_code cellMusicDecodeRead(vm::ptr buf, vm::ptr startTime, u64 reqSize, vm::ptr readSize, vm::ptr position) { - cellMusicDecode.todo("cellMusicDecodeRead(buf=*0x%x, startTime=*0x%x, reqSize=0x%llx, readSize=*0x%x, position=*0x%x)", buf, startTime, reqSize, readSize, position); - return CELL_OK; + cellMusicDecode.notice("cellMusicDecodeRead(buf=*0x%x, startTime=*0x%x, reqSize=0x%llx, readSize=*0x%x, position=*0x%x)", buf, startTime, reqSize, readSize, position); + + return cell_music_decode_read(buf, startTime, reqSize, readSize, position); } error_code cellMusicDecodeGetSelectionContext(vm::ptr context) { cellMusicDecode.todo("cellMusicDecodeGetSelectionContext(context=*0x%x)", context); + + if (!context) + return CELL_MUSIC_DECODE_ERROR_PARAM; + + auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); + *context = dec.current_selection_context.get(); + cellMusicDecode.warning("cellMusicDecodeGetSelectionContext: selection_context = %s", dec.current_selection_context.to_string()); + return CELL_OK; } @@ -209,14 +361,23 @@ error_code cellMusicDecodeSetSelectionContext(vm::ptr { cellMusicDecode.todo("cellMusicDecodeSetSelectionContext(context=*0x%x)", context); + if (!context) + return CELL_MUSIC_DECODE_ERROR_PARAM; + auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); if (!dec.func) return CELL_MUSIC_DECODE_ERROR_GENERIC; - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 + const bool result = dec.current_selection_context.set(*context); + if (result) cellMusicDecode.warning("cellMusicDecodeSetSelectionContext: new selection_context = %s", dec.current_selection_context.to_string()); + else cellMusicDecode.error("cellMusicDecodeSetSelectionContext: failed. context = '%s'", context->data); + + sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32 { - dec.func(ppu, CELL_MUSIC_DECODE_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(CELL_OK), dec.userData); + const u32 status = result ? u32{CELL_OK} : u32{CELL_MUSIC_DECODE_ERROR_INVALID_CONTEXT}; + dec.func(ppu, CELL_MUSIC_DECODE_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(status), dec.userData); return CELL_OK; }); @@ -226,37 +387,53 @@ error_code cellMusicDecodeSetSelectionContext(vm::ptr error_code cellMusicDecodeGetContentsId(vm::ptr contents_id) { cellMusicDecode.todo("cellMusicDecodeGetContentsId(contents_id=*0x%x)", contents_id); - return CELL_OK; + + if (!contents_id) + return CELL_MUSIC_ERROR_PARAM; + + // HACKY + auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); + return dec.current_selection_context.find_content_id(contents_id); } -error_code cellMusicDecodeInitialize2(s32 mode, u32 container, s32 spuPriority, vm::ptr func, vm::ptr userData, s32 speed, s32 bufsize) +error_code cellMusicDecodeInitialize2(s32 mode, u32 container, s32 spuPriority, vm::ptr func, vm::ptr userData, s32 speed, s32 bufSize) { - cellMusicDecode.todo("cellMusicDecodeInitialize2(mode=0x%x, container=0x%x, spuPriority=0x%x, func=*0x%x, userData=*0x%x, speed=0x%x, bufsize=0x%x)", mode, container, spuPriority, func, userData, speed, bufsize); + cellMusicDecode.todo("cellMusicDecodeInitialize2(mode=0x%x, container=0x%x, spuPriority=0x%x, func=*0x%x, userData=*0x%x, speed=0x%x, bufSize=0x%x)", mode, container, spuPriority, func, userData, speed, bufSize); + + if (bufSize < CELL_MUSIC_DECODE2_MIN_BUFFER_SIZE) + return CELL_MUSIC_DECODE_ERROR_PARAM; auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); dec.func = func; dec.userData = userData; + dec.speed = speed; - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 + sysutil_register_cb([userData, &dec](ppu_thread& ppu) -> s32 { - dec.func(ppu, CELL_MUSIC_DECODE2_EVENT_INITIALIZE_RESULT, vm::addr_t(CELL_OK), userData); + dec.func(ppu, CELL_MUSIC_DECODE_EVENT_INITIALIZE_RESULT, vm::addr_t(CELL_OK), userData); return CELL_OK; }); return CELL_OK; } -error_code cellMusicDecodeInitialize2SystemWorkload(s32 mode, u32 container, vm::ptr func, vm::ptr userData, s32 spuUsageRate, s32 bufsize, vm::ptr spurs, vm::cptr priority, vm::cptr attr) +error_code cellMusicDecodeInitialize2SystemWorkload(s32 mode, u32 container, vm::ptr func, vm::ptr userData, s32 spuUsageRate, s32 bufSize, vm::ptr spurs, vm::cptr priority, vm::cptr attr) { - cellMusicDecode.todo("cellMusicDecodeInitialize2SystemWorkload(mode=0x%x, container=0x%x, func=*0x%x, userData=*0x%x, spuUsageRate=0x%x, bufsize=0x%x, spurs=*0x%x, priority=*0x%x, attr=*0x%x)", mode, container, func, userData, spuUsageRate, bufsize, spurs, priority, attr); + cellMusicDecode.todo("cellMusicDecodeInitialize2SystemWorkload(mode=0x%x, container=0x%x, func=*0x%x, userData=*0x%x, spuUsageRate=0x%x, bufSize=0x%x, spurs=*0x%x, priority=*0x%x, attr=*0x%x)", mode, container, func, userData, spuUsageRate, bufSize, spurs, priority, attr); + + if (bufSize < CELL_MUSIC_DECODE2_MIN_BUFFER_SIZE) + return CELL_MUSIC_DECODE_ERROR_PARAM; auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); dec.func = func; dec.userData = userData; - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 + sysutil_register_cb([&dec](ppu_thread& ppu) -> s32 { - dec.func(ppu, CELL_MUSIC_DECODE2_EVENT_INITIALIZE_RESULT, vm::addr_t(CELL_OK), userData); + dec.func(ppu, CELL_MUSIC_DECODE_EVENT_INITIALIZE_RESULT, vm::addr_t(CELL_OK), dec.userData); return CELL_OK; }); @@ -268,12 +445,14 @@ error_code cellMusicDecodeFinalize2() cellMusicDecode.todo("cellMusicDecodeFinalize2()"); auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); + dec.finalize(); if (dec.func) { - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 + sysutil_register_cb([&dec](ppu_thread& ppu) -> s32 { - dec.func(ppu, CELL_MUSIC_DECODE2_EVENT_FINALIZE_RESULT, vm::addr_t(CELL_OK), dec.userData); + dec.func(ppu, CELL_MUSIC_DECODE_EVENT_FINALIZE_RESULT, vm::addr_t(CELL_OK), dec.userData); return CELL_OK; }); } @@ -286,13 +465,14 @@ error_code cellMusicDecodeSelectContents2() cellMusicDecode.todo("cellMusicDecodeSelectContents2()"); auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); if (!dec.func) return CELL_MUSIC_DECODE_ERROR_GENERIC; sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 { - dec.func(ppu, CELL_MUSIC_DECODE2_EVENT_SELECT_CONTENTS_RESULT, vm::addr_t(CELL_OK), dec.userData); + dec.func(ppu, CELL_MUSIC_DECODE_EVENT_SELECT_CONTENTS_RESULT, vm::addr_t(CELL_OK), dec.userData); return CELL_OK; }); @@ -304,13 +484,16 @@ error_code cellMusicDecodeSetDecodeCommand2(s32 command) cellMusicDecode.todo("cellMusicDecodeSetDecodeCommand2(command=0x%x)", command); auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); if (!dec.func) return CELL_MUSIC_DECODE_ERROR_GENERIC; - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 + const error_code result = dec.set_decode_command(command); + + sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32 { - dec.func(ppu, CELL_MUSIC_DECODE2_EVENT_SET_DECODE_COMMAND_RESULT, vm::addr_t(CELL_OK), dec.userData); + dec.func(ppu, CELL_MUSIC_DECODE_EVENT_SET_DECODE_COMMAND_RESULT, vm::addr_t(s32{result}), dec.userData); return CELL_OK; }); @@ -320,18 +503,36 @@ error_code cellMusicDecodeSetDecodeCommand2(s32 command) error_code cellMusicDecodeGetDecodeStatus2(vm::ptr status) { cellMusicDecode.todo("cellMusicDecodeGetDecodeStatus2(status=*0x%x)", status); + + if (!status) + return CELL_MUSIC_DECODE_ERROR_PARAM; + + auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); + *status = dec.decode_status; + return CELL_OK; } error_code cellMusicDecodeRead2(vm::ptr buf, vm::ptr startTime, u64 reqSize, vm::ptr readSize, vm::ptr position) { - cellMusicDecode.todo("cellMusicDecodeRead2(buf=*0x%x, startTime=*0x%x, reqSize=0x%llx, readSize=*0x%x, position=*0x%x)", buf, startTime, reqSize, readSize, position); - return CELL_OK; + cellMusicDecode.notice("cellMusicDecodeRead2(buf=*0x%x, startTime=*0x%x, reqSize=0x%llx, readSize=*0x%x, position=*0x%x)", buf, startTime, reqSize, readSize, position); + + return cell_music_decode_read(buf, startTime, reqSize, readSize, position); } error_code cellMusicDecodeGetSelectionContext2(vm::ptr context) { cellMusicDecode.todo("cellMusicDecodeGetSelectionContext2(context=*0x%x)", context); + + if (!context) + return CELL_MUSIC_DECODE_ERROR_PARAM; + + auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); + *context = dec.current_selection_context.get(); + cellMusicDecode.warning("cellMusicDecodeGetSelectionContext2: selection context = %s)", dec.current_selection_context.to_string()); + return CELL_OK; } @@ -340,23 +541,36 @@ error_code cellMusicDecodeSetSelectionContext2(vm::ptrget(); + std::lock_guard lock(dec.mutex); if (!dec.func) return CELL_MUSIC_DECODE_ERROR_GENERIC; - sysutil_register_cb([=, &dec](ppu_thread& ppu) -> s32 + const bool result = dec.current_selection_context.set(*context); + if (result) cellMusicDecode.warning("cellMusicDecodeSetSelectionContext2: new selection_context = %s", dec.current_selection_context.to_string()); + else cellMusicDecode.error("cellMusicDecodeSetSelectionContext2: failed. context = '%s'", context->data); + + sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32 { - dec.func(ppu, CELL_MUSIC_DECODE2_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(CELL_OK), dec.userData); + const u32 status = result ? u32{CELL_OK} : u32{CELL_MUSIC_DECODE_ERROR_INVALID_CONTEXT}; + dec.func(ppu, CELL_MUSIC_DECODE_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(status), dec.userData); return CELL_OK; }); return CELL_OK; } -error_code cellMusicDecodeGetContentsId2(vm::ptr contents_id ) +error_code cellMusicDecodeGetContentsId2(vm::ptr contents_id) { cellMusicDecode.todo("cellMusicDecodeGetContentsId2(contents_id=*0x%x)", contents_id); - return CELL_OK; + + if (!contents_id) + return CELL_MUSIC2_ERROR_PARAM; + + // HACKY + auto& dec = g_fxo->get(); + std::lock_guard lock(dec.mutex); + return dec.current_selection_context.find_content_id(contents_id); } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_user_list_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_user_list_dialog.cpp index ac1efd17c1..a00fe7a975 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_user_list_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_user_list_dialog.cpp @@ -25,6 +25,7 @@ namespace rsx else { // Fallback + // TODO: use proper icon static_cast(image.get())->set_image_resource(resource_config::standard_image_resource::square); } @@ -192,7 +193,6 @@ namespace rsx m_dim_background->back_color.a = 0.5f; } - std::vector icon; std::vector> entries; const std::string home_dir = rpcs3::utils::get_hdd0_dir() + "home/"; diff --git a/rpcs3/util/media_utils.cpp b/rpcs3/util/media_utils.cpp index 888ba990f3..725659c3b5 100644 --- a/rpcs3/util/media_utils.cpp +++ b/rpcs3/util/media_utils.cpp @@ -7,12 +7,18 @@ #pragma warning(push, 0) #else #pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wall" +#pragma GCC diagnostic ignored "-Wextra" #pragma GCC diagnostic ignored "-Wold-style-cast" #endif extern "C" { +#include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavutil/dict.h" +#include "libavutil/opt.h" +#include "libswresample/swresample.h" } +constexpr int averror_eof = AVERROR_EOF; // workaround for old-style-cast error #ifdef _MSC_VER #pragma warning(pop) #else @@ -156,4 +162,257 @@ namespace utils return { true, std::move(info) }; } + + struct scoped_av + { + AVFormatContext* format = nullptr; + AVCodec* codec = nullptr; + AVCodecContext* context = nullptr; + AVFrame* frame = nullptr; + SwrContext* swr = nullptr; + + ~scoped_av() + { + // Clean up + if (frame) + av_frame_free(&frame); + if (swr) + swr_free(&swr); + if (context) + avcodec_close(context); + if (codec) + av_free(codec); + if (format) + avformat_free_context(format); + } + }; + + audio_decoder::audio_decoder() + { + } + + audio_decoder::~audio_decoder() + { + stop(); + } + + void audio_decoder::set_path(const std::string& path) + { + m_path = path; + } + + void audio_decoder::set_swap_endianness(bool swapped) + { + m_swap_endianness = swapped; + } + + void audio_decoder::stop() + { + if (m_thread) + { + auto& thread = *m_thread; + thread = thread_state::aborting; + thread(); + } + + has_error = false; + m_size = 0; + timestamps_ms.clear(); + data.clear(); + } + + void audio_decoder::decode() + { + stop(); + + m_thread = std::make_unique>>("Music Decode Thread", [this, path = m_path]() + { + scoped_av av; + + // Get format from audio file + av.format = avformat_alloc_context(); + if (int err = avformat_open_input(&av.format, path.c_str(), nullptr, nullptr); err < 0) + { + media_log.error("audio_decoder: Could not open file '%s'. Error: %d='%s'", path, err, error_to_string(err)); + has_error = true; + return; + } + if (int err = avformat_find_stream_info(av.format, nullptr); err < 0) + { + media_log.error("audio_decoder: Could not retrieve stream info from file '%s'. Error: %d='%s'", path, err, error_to_string(err)); + has_error = true; + return; + } + + // Find the first audio stream + AVStream* stream = nullptr; + unsigned int stream_index; + for (stream_index = 0; stream_index < av.format->nb_streams; stream_index++) + { + if (av.format->streams[stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + { + stream = av.format->streams[stream_index]; + break; + } + } + if (!stream) + { + media_log.error("audio_decoder: Could not retrieve audio stream from file '%s'", path); + has_error = true; + return; + } + + // Find decoder + av.codec = avcodec_find_decoder(stream->codecpar->codec_id); + if (!av.codec) + { + media_log.error("audio_decoder: Failed to find decoder for stream #%u in file '%s'", stream_index, path); + has_error = true; + return; + } + + // Allocate context + av.context = avcodec_alloc_context3(av.codec); + if (!av.context) + { + media_log.error("audio_decoder: Failed to allocate context for stream #%u in file '%s'", stream_index, path); + has_error = true; + return; + } + + // Open decoder + if (int err = avcodec_open2(av.context, av.codec, nullptr); err < 0) + { + media_log.error("audio_decoder: Failed to open decoder for stream #%u in file '%s'. Error: %d='%s'", stream_index, path, err, error_to_string(err)); + has_error = true; + return; + } + + // Prepare resampler + av.swr = swr_alloc(); + if (!av.swr) + { + media_log.error("audio_decoder: Failed to allocate resampler for stream #%u in file '%s'", stream_index, path); + has_error = true; + return; + } + + const int dst_channels = 2; + const u64 dst_channel_layout = AV_CH_LAYOUT_STEREO; + const AVSampleFormat dst_format = AV_SAMPLE_FMT_FLT; + + int set_err = 0; + if ((set_err = av_opt_set_int(av.swr, "in_channel_count", stream->codecpar->channels, 0)) || + (set_err = av_opt_set_int(av.swr, "out_channel_count", dst_channels, 0)) || + (set_err = av_opt_set_channel_layout(av.swr, "in_channel_layout", stream->codecpar->channel_layout, 0)) || + (set_err = av_opt_set_channel_layout(av.swr, "out_channel_layout", dst_channel_layout, 0)) || + (set_err = av_opt_set_int(av.swr, "in_sample_rate", stream->codecpar->sample_rate, 0)) || + (set_err = av_opt_set_int(av.swr, "out_sample_rate", sample_rate, 0)) || + (set_err = av_opt_set_sample_fmt(av.swr, "in_sample_fmt", static_cast(stream->codecpar->format), 0)) || + (set_err = av_opt_set_sample_fmt(av.swr, "out_sample_fmt", dst_format, 0))) + { + media_log.error("audio_decoder: Failed to set resampler options: Error: %d='%s'", set_err, error_to_string(set_err)); + has_error = true; + return; + } + + if (int err = swr_init(av.swr); err < 0 || !swr_is_initialized(av.swr)) + { + media_log.error("audio_decoder: Resampler has not been properly initialized: %d='%s'", err, error_to_string(err)); + has_error = true; + return; + } + + // Prepare to read data + AVPacket packet; + av_init_packet(&packet); + av.frame = av_frame_alloc(); + if (!av.frame) + { + media_log.error("audio_decoder: Error allocating the frame"); + has_error = true; + return; + } + + duration_ms = stream->duration / 1000; + + // Iterate through frames + while (thread_ctrl::state() != thread_state::aborting && av_read_frame(av.format, &packet) >= 0) + { + if (int err = avcodec_send_packet(av.context, &packet); err < 0) + { + media_log.error("audio_decoder: Queuing error: %d='%s'", err, error_to_string(err)); + has_error = true; + return; + } + + while (thread_ctrl::state() != thread_state::aborting) + { + if (int err = avcodec_receive_frame(av.context, av.frame); err < 0) + { + if (err == AVERROR(EAGAIN) || err == averror_eof) + break; + + media_log.error("audio_decoder: Decoding error: %d='%s'", err, error_to_string(err)); + has_error = true; + return; + } + + // Resample frames + u8* buffer; + const int align = 1; + const int buffer_size = av_samples_alloc(&buffer, nullptr, dst_channels, av.frame->nb_samples, dst_format, align); + if (buffer_size < 0) + { + media_log.error("audio_decoder: Error allocating buffer: %d='%s'", buffer_size, error_to_string(buffer_size)); + has_error = true; + return; + } + + const int frame_count = swr_convert(av.swr, &buffer, av.frame->nb_samples, const_cast(av.frame->data), av.frame->nb_samples); + if (frame_count < 0) + { + media_log.error("audio_decoder: Error converting frame: %d='%s'", frame_count, error_to_string(frame_count)); + has_error = true; + if (buffer) + av_free(buffer); + return; + } + + // Append resampled frames to data + { + std::scoped_lock lock(m_mtx); + data.resize(m_size + buffer_size); + + if (m_swap_endianness) + { + // The format is float 32bit per channel. + const auto write_byteswapped = [](const void* src, void* dst) -> void + { + *static_cast(dst) = *static_cast*>(src); + }; + + for (size_t i = 0; i < (buffer_size - sizeof(f32)); i += sizeof(f32)) + { + write_byteswapped(buffer + i, data.data() + m_size + i); + } + } + else + { + memcpy(&data[m_size], buffer, buffer_size); + } + + const u32 timestamp_ms = stream->time_base.den ? (1000 * av.frame->best_effort_timestamp * stream->time_base.num) / stream->time_base.den : 0; + timestamps_ms.push_back({m_size, timestamp_ms}); + m_size += buffer_size; + } + + if (buffer) + av_free(buffer); + + media_log.trace("audio_decoder: decoded frame_count=%d buffer_size=%d timestamp_us=%d", frame_count, buffer_size, av.frame->best_effort_timestamp); + } + } + }); + } } diff --git a/rpcs3/util/media_utils.h b/rpcs3/util/media_utils.h index 5635812565..7e7c1e1bad 100644 --- a/rpcs3/util/media_utils.h +++ b/rpcs3/util/media_utils.h @@ -1,6 +1,12 @@ #pragma once #include +#include +#include +#include +#include +#include "Utilities/StrUtil.h" +#include "Utilities/Thread.h" namespace utils { @@ -34,4 +40,29 @@ namespace utils } strcpy_trunc(dst, value); }; + + class audio_decoder + { + public: + audio_decoder(); + ~audio_decoder(); + + void set_path(const std::string& path); + void set_swap_endianness(bool swapped); + void stop(); + void decode(); + + std::mutex m_mtx; + const s32 sample_rate = 48000; + std::vector data; + atomic_t m_size = 0; + atomic_t duration_ms = 0; + atomic_t has_error{false}; + std::deque> timestamps_ms; + + private: + bool m_swap_endianness = false; + std::string m_path; + std::unique_ptr>> m_thread; + }; }