From b5c69189e56d8813b10adb2d2bcf17426b3bdac0 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Fri, 26 Jul 2024 18:34:36 +0300 Subject: [PATCH] avplayer WIP --- .gitmodules | 3 + CMakeLists.txt | 12 +- externals/CMakeLists.txt | 5 + externals/ffmpeg-core | 1 + src/common/logging/filter.cpp | 2 +- src/core/file_sys/fs.cpp | 4 +- src/core/file_sys/fs.h | 2 +- src/core/libraries/audio/audioout.cpp | 5 +- src/core/libraries/avplayer/avplayer.cpp | 396 ++++++++--- src/core/libraries/avplayer/avplayer.h | 302 +++++++- .../libraries/avplayer/avplayer_common.cpp | 120 ++++ src/core/libraries/avplayer/avplayer_common.h | 179 +++++ .../avplayer/avplayer_data_streamer.h | 20 + .../avplayer/avplayer_file_streamer.cpp | 93 +++ .../avplayer/avplayer_file_streamer.h | 40 ++ src/core/libraries/avplayer/avplayer_impl.cpp | 186 +++++ src/core/libraries/avplayer/avplayer_impl.h | 65 ++ .../libraries/avplayer/avplayer_source.cpp | 658 ++++++++++++++++++ src/core/libraries/avplayer/avplayer_source.h | 169 +++++ .../libraries/avplayer/avplayer_state.cpp | 481 +++++++++++++ src/core/libraries/avplayer/avplayer_state.h | 85 +++ src/core/libraries/error_codes.h | 13 + src/core/libraries/kernel/thread_management.h | 16 +- src/core/libraries/kernel/time_management.h | 1 + 24 files changed, 2721 insertions(+), 137 deletions(-) create mode 160000 externals/ffmpeg-core create mode 100644 src/core/libraries/avplayer/avplayer_common.cpp create mode 100644 src/core/libraries/avplayer/avplayer_common.h create mode 100644 src/core/libraries/avplayer/avplayer_data_streamer.h create mode 100644 src/core/libraries/avplayer/avplayer_file_streamer.cpp create mode 100644 src/core/libraries/avplayer/avplayer_file_streamer.h create mode 100644 src/core/libraries/avplayer/avplayer_impl.cpp create mode 100644 src/core/libraries/avplayer/avplayer_impl.h create mode 100644 src/core/libraries/avplayer/avplayer_source.cpp create mode 100644 src/core/libraries/avplayer/avplayer_source.h create mode 100644 src/core/libraries/avplayer/avplayer_state.cpp create mode 100644 src/core/libraries/avplayer/avplayer_state.h diff --git a/.gitmodules b/.gitmodules index 3a9d8f42..e96f33ec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -61,3 +61,6 @@ [submodule "externals/date"] path = externals/date url = https://github.com/HowardHinnant/date.git +[submodule "externals/ffmpeg-core"] + path = externals/ffmpeg-core + url = https://github.com/RPCS3/ffmpeg-core.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 3685b7f8..c01e9e98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,16 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/disc_map/disc_map.cpp src/core/libraries/disc_map/disc_map.h src/core/libraries/disc_map/disc_map_codes.h + src/core/libraries/avplayer/avplayer_common.cpp + src/core/libraries/avplayer/avplayer_common.h + src/core/libraries/avplayer/avplayer_file_streamer.cpp + src/core/libraries/avplayer/avplayer_file_streamer.h + src/core/libraries/avplayer/avplayer_impl.cpp + src/core/libraries/avplayer/avplayer_impl.h + src/core/libraries/avplayer/avplayer_source.cpp + src/core/libraries/avplayer/avplayer_source.h + src/core/libraries/avplayer/avplayer_state.cpp + src/core/libraries/avplayer/avplayer_state.h src/core/libraries/avplayer/avplayer.cpp src/core/libraries/avplayer/avplayer.h ) @@ -588,7 +598,7 @@ endif() 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) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API ffmpeg) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3) if (APPLE) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 9ebdd878..bc313232 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -47,6 +47,11 @@ else() endif() endif() +if (NOT TARGET ffmpeg) + set(ARCHITECTURE "x86_64") + add_subdirectory(ffmpeg-core) +endif() + # Zlib-Ng if (NOT TARGET zlib-ng::zlib) set(ZLIB_ENABLE_TESTS OFF) diff --git a/externals/ffmpeg-core b/externals/ffmpeg-core new file mode 160000 index 00000000..e30b7d7f --- /dev/null +++ b/externals/ffmpeg-core @@ -0,0 +1 @@ +Subproject commit e30b7d7fe228bfb3f6e41ce1040b44a15eb7d5e0 diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index a514652d..2c4a20de 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -106,12 +106,12 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, DiscMap) \ SUB(Lib, Png) \ SUB(Lib, PlayGo) \ + SUB(Lib, Random) \ SUB(Lib, Usbd) \ SUB(Lib, Ajm) \ SUB(Lib, ErrorDialog) \ SUB(Lib, ImeDialog) \ SUB(Lib, AvPlayer) \ - SUB(Lib, Random) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Vulkan) \ diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index a6d5c3ea..40d8212b 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -25,9 +25,9 @@ void MntPoints::UnmountAll() { m_mnt_pairs.clear(); } -std::filesystem::path MntPoints::GetHostPath(const std::string& guest_directory) { +std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) { // Evil games like Turok2 pass double slashes e.g /app0//game.kpf - auto corrected_path = guest_directory; + std::string corrected_path(guest_directory); size_t pos = corrected_path.find("//"); while (pos != std::string::npos) { corrected_path.replace(pos, 2, "/"); diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index d636f8bf..b0fb6324 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -31,7 +31,7 @@ public: void Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder); void UnmountAll(); - std::filesystem::path GetHostPath(const std::string& guest_directory); + std::filesystem::path GetHostPath(std::string_view guest_directory); const MntPair* GetMount(const std::string& guest_path) { const auto it = std::ranges::find_if( diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index eac3845f..08929383 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -175,7 +175,6 @@ int PS4_SYSV_ABI sceAudioOutGetLastOutputTime() { } int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) { - int type = 0; int channels_num = 0; @@ -235,11 +234,11 @@ int PS4_SYSV_ABI sceAudioOutGetSystemState() { } int PS4_SYSV_ABI sceAudioOutInit() { + LOG_INFO(Lib_AudioOut, "called"); if (audio != nullptr) { return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT; } audio = std::make_unique(); - LOG_INFO(Lib_AudioOut, "called"); return ORBIS_OK; } @@ -324,10 +323,12 @@ int PS4_SYSV_ABI sceAudioOutOpenEx() { } s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) { + LOG_TRACE(Lib_AudioOut, "called"); return audio->AudioOutOutput(handle, ptr); } int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) { + LOG_TRACE(Lib_AudioOut, "called"); for (u32 i = 0; i < num; i++) { if (auto err = audio->AudioOutOutput(param[i].handle, param[i].ptr); err != 0) return err; diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index dd9f42b2..41f8d076 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -1,21 +1,36 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// Generated By moduleGenerator #include "avplayer.h" + +#include "avplayer_impl.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" +#include "core/libraries/kernel/thread_management.h" #include "core/libraries/libs.h" +#include + namespace Libraries::AvPlayer { -int PS4_SYSV_ABI sceAvPlayerAddSource() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; +using namespace Kernel; + +s32 PS4_SYSV_ABI sceAvPlayerAddSource(SceAvPlayerHandle handle, const char* filename) { + LOG_TRACE(Lib_AvPlayer, "filename = {}", filename); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->AddSource(filename); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; } -int PS4_SYSV_ABI sceAvPlayerAddSourceEx() { +s32 PS4_SYSV_ABI sceAvPlayerAddSourceEx(SceAvPlayerHandle handle, SceAvPlayerUriType uriType, + SceAvPlayerSourceDetails* sourceDetails) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } return ORBIS_OK; } @@ -24,122 +39,307 @@ int PS4_SYSV_ABI sceAvPlayerChangeStream() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAvPlayerClose() { +s32 PS4_SYSV_ABI sceAvPlayerClose(SceAvPlayerHandle handle) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + delete handle; + return ORBIS_OK; +} + +u64 PS4_SYSV_ABI sceAvPlayerCurrentTime(SceAvPlayerHandle handle) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->CurrentTime(); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +s32 PS4_SYSV_ABI sceAvPlayerDisableStream(SceAvPlayerHandle handle, u32 stream_id) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAvPlayerEnableStream(SceAvPlayerHandle handle, u32 stream_id) { + LOG_TRACE(Lib_AvPlayer, "stream_id = {}", stream_id); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->EnableStream(stream_id); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +bool PS4_SYSV_ABI sceAvPlayerGetAudioData(SceAvPlayerHandle handle, SceAvPlayerFrameInfo* p_info) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr || p_info == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->GetAudioData(*p_info); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +s32 PS4_SYSV_ABI sceAvPlayerGetStreamInfo(SceAvPlayerHandle handle, u32 stream_id, + SceAvPlayerStreamInfo* p_info) { + LOG_TRACE(Lib_AvPlayer, "stream_id = {}", stream_id); + if (handle == nullptr || p_info == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->GetStreamInfo(stream_id, *p_info); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +bool PS4_SYSV_ABI sceAvPlayerGetVideoData(SceAvPlayerHandle handle, + SceAvPlayerFrameInfo* video_info) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr || video_info == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->GetVideoData(*video_info); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +bool PS4_SYSV_ABI sceAvPlayerGetVideoDataEx(SceAvPlayerHandle handle, + SceAvPlayerFrameInfoEx* video_info) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr || video_info == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->GetVideoData(*video_info); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +constexpr u32 GetPriority(u32 base, u32 offset) { + // (27D <= base_priority <= 2FC) + offset <= 2FF + return std::min(std::min(std::max(637u, base), 764u) + offset, 767u); +} + +SceAvPlayerHandle PS4_SYSV_ABI sceAvPlayerInit(SceAvPlayerInitData* data) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (data == nullptr) { + return nullptr; + } + + if (data->memory_replacement.allocate == nullptr || + data->memory_replacement.allocate_texture == nullptr || + data->memory_replacement.deallocate == nullptr || + data->memory_replacement.deallocate_texture == nullptr) { + LOG_ERROR(Lib_AvPlayer, "All allocators are required for AVPlayer Initialisation."); + return nullptr; + } + + ThreadPriorities priorities{}; + const u32 base_priority = data->base_priority != 0 ? data->base_priority : 700; + priorities.video_decoder_priority = GetPriority(base_priority, 5); + priorities.audio_decoder_priority = GetPriority(base_priority, 6); + priorities.demuxer_priority = GetPriority(base_priority, 9); + priorities.controller_priority = GetPriority(base_priority, 2); + // priorities.http_streaming_priority = GetPriority(base_priority, 10); + // priorities.file_streaming_priority = GetPriority(priorities.http_streaming_priority, 15); + // priorities.maxPriority = priorities.http_streaming_priority; + + const auto player = new AvPlayer(); + player->Init(*data, priorities); + return player; +} + +s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data, + SceAvPlayerHandle* p_player) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (p_data == nullptr || p_player == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + + if (p_data->memory_replacement.allocate == nullptr || + p_data->memory_replacement.allocate_texture == nullptr || + p_data->memory_replacement.deallocate == nullptr || + p_data->memory_replacement.deallocate_texture == nullptr) { + LOG_ERROR(Lib_AvPlayer, "All allocators are required for AVPlayer Initialisation."); + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + + SceAvPlayerInitData data = {}; + data.memory_replacement = p_data->memory_replacement; + data.file_replacement = p_data->file_replacement; + data.event_replacement = p_data->event_replacement; + data.default_language = p_data->default_language; + data.num_output_video_framebuffers = p_data->num_output_video_framebuffers; + data.auto_start = p_data->auto_start; + + ThreadPriorities priorities{}; + s32 base_priority = 0; + const auto res = scePthreadGetprio(scePthreadSelf(), &base_priority); + if (res != 0 || base_priority == 0) { + base_priority = 700; + } + + if (p_data->video_decoder_priority != 0) { + priorities.video_decoder_priority = p_data->video_decoder_priority; + } else { + priorities.video_decoder_priority = GetPriority(base_priority, 5); + } + priorities.video_decoder_affinity = p_data->video_decoder_affinity; + + if (p_data->audio_decoder_priority != 0) { + priorities.audio_decoder_priority = p_data->audio_decoder_priority; + } else { + priorities.audio_decoder_priority = GetPriority(base_priority, 6); + } + priorities.audio_decoder_affinity = p_data->audio_decoder_affinity; + + if (p_data->controller_priority != 0) { + priorities.controller_priority = p_data->controller_priority; + } else { + priorities.controller_priority = GetPriority(base_priority, 2); + } + priorities.controller_affinity = p_data->controller_affinity; + + if (p_data->demuxer_priority != 0) { + priorities.demuxer_priority = p_data->demuxer_priority; + } else { + priorities.demuxer_priority = GetPriority(base_priority, 9); + } + priorities.demuxer_affinity = p_data->demuxer_affinity; + + // if (p_data->http_streaming_priority != 0) { + // priorities.http_streaming_priority = p_data->http_streaming_priority; + // } else { + // priorities.http_streaming_priority = GetPriority(base_priority, 10); + // } + // priorities.http_streaming_affinity = p_data->http_streaming_affinity; + + // if (p_data->file_streaming_priority != 0) { + // priorities.file_streaming_priority = p_data->file_streaming_priority; + // } else { + // priorities.file_streaming_priority = GetPriority(base_priority, 15); + // } + // priorities.http_streaming_affinity = p_data->http_streaming_affinity; + + const auto player = new AvPlayer(); + player->Init(data, priorities); + *p_player = player; + return ORBIS_OK; +} + +bool PS4_SYSV_ABI sceAvPlayerIsActive(SceAvPlayerHandle handle) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr) { + LOG_TRACE(Lib_AvPlayer, "returning ORBIS_AVPLAYER_ERROR_INVALID_PARAMS"); + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->IsActive(); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +s32 PS4_SYSV_ABI sceAvPlayerJumpToTime(SceAvPlayerHandle handle, uint64_t jump_time_msec) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAvPlayerPause(SceAvPlayerHandle handle) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAvPlayerPostInit(SceAvPlayerHandle handle, SceAvPlayerPostInitData* data) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr || data == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->PostInit(*data); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +s32 PS4_SYSV_ABI sceAvPlayerPrintf(const char* format, ...) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAvPlayerCurrentTime() { +s32 PS4_SYSV_ABI sceAvPlayerResume(SceAvPlayerHandle handle) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAvPlayerSetAvSyncMode(SceAvPlayerHandle handle, + SceAvPlayerAvSyncMode sync_mode) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAvPlayerSetLogCallback(SceAvPlayerLogCallback logCb, void* user_data) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAvPlayerDisableStream() { +s32 PS4_SYSV_ABI sceAvPlayerSetLooping(SceAvPlayerHandle handle, bool loop_flag) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } return ORBIS_OK; } -int PS4_SYSV_ABI sceAvPlayerEnableStream() { +s32 PS4_SYSV_ABI sceAvPlayerSetTrickSpeed(SceAvPlayerHandle handle, s32 trick_speed) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } return ORBIS_OK; } -int PS4_SYSV_ABI sceAvPlayerGetAudioData() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceAvPlayerStart(SceAvPlayerHandle handle) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->Start(); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; } -int PS4_SYSV_ABI sceAvPlayerGetStreamInfo() { +s32 PS4_SYSV_ABI sceAvPlayerStop(SceAvPlayerHandle handle) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return handle->Stop(); } -int PS4_SYSV_ABI sceAvPlayerGetVideoData() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceAvPlayerStreamCount(SceAvPlayerHandle handle) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->GetStreamCount(); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; } -int PS4_SYSV_ABI sceAvPlayerGetVideoDataEx() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerInit() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerInitEx() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerIsActive() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerJumpToTime() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerPause() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerPostInit() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerPrintf() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerResume() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerSetAvSyncMode() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerSetLogCallback() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerSetLooping() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerSetTrickSpeed() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerStart() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerStop() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerStreamCount() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerVprintf() { +s32 PS4_SYSV_ABI sceAvPlayerVprintf(const char* format, va_list args) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); return ORBIS_OK; } diff --git a/src/core/libraries/avplayer/avplayer.h b/src/core/libraries/avplayer/avplayer.h index 39a619ee..f5589441 100644 --- a/src/core/libraries/avplayer/avplayer.h +++ b/src/core/libraries/avplayer/avplayer.h @@ -11,33 +11,279 @@ class SymbolsResolver; namespace Libraries::AvPlayer { -int PS4_SYSV_ABI sceAvPlayerAddSource(); -int PS4_SYSV_ABI sceAvPlayerAddSourceEx(); -int PS4_SYSV_ABI sceAvPlayerChangeStream(); -int PS4_SYSV_ABI sceAvPlayerClose(); -int PS4_SYSV_ABI sceAvPlayerCurrentTime(); -int PS4_SYSV_ABI sceAvPlayerDisableStream(); -int PS4_SYSV_ABI sceAvPlayerEnableStream(); -int PS4_SYSV_ABI sceAvPlayerGetAudioData(); -int PS4_SYSV_ABI sceAvPlayerGetStreamInfo(); -int PS4_SYSV_ABI sceAvPlayerGetVideoData(); -int PS4_SYSV_ABI sceAvPlayerGetVideoDataEx(); -int PS4_SYSV_ABI sceAvPlayerInit(); -int PS4_SYSV_ABI sceAvPlayerInitEx(); -int PS4_SYSV_ABI sceAvPlayerIsActive(); -int PS4_SYSV_ABI sceAvPlayerJumpToTime(); -int PS4_SYSV_ABI sceAvPlayerPause(); -int PS4_SYSV_ABI sceAvPlayerPostInit(); -int PS4_SYSV_ABI sceAvPlayerPrintf(); -int PS4_SYSV_ABI sceAvPlayerResume(); -int PS4_SYSV_ABI sceAvPlayerSetAvSyncMode(); -int PS4_SYSV_ABI sceAvPlayerSetLogCallback(); -int PS4_SYSV_ABI sceAvPlayerSetLooping(); -int PS4_SYSV_ABI sceAvPlayerSetTrickSpeed(); -int PS4_SYSV_ABI sceAvPlayerStart(); -int PS4_SYSV_ABI sceAvPlayerStop(); -int PS4_SYSV_ABI sceAvPlayerStreamCount(); -int PS4_SYSV_ABI sceAvPlayerVprintf(); +class AvPlayer; + +using SceAvPlayerHandle = AvPlayer*; + +enum SceAvPlayerUriType { SCE_AVPLAYER_URI_TYPE_SOURCE = 0 }; + +struct SceAvPlayerUri { + const char* name; + u32 length; +}; + +enum SceAvPlayerSourceType { + SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN = 0, + SCE_AVPLAYER_SOURCE_TYPE_FILE_MP4 = 1, + SCE_AVPLAYER_SOURCE_TYPE_HLS = 8 +}; + +struct SceAvPlayerSourceDetails { + SceAvPlayerUri uri; + u8 reserved1[64]; + SceAvPlayerSourceType source_type; + u8 reserved2[44]; +}; + +struct SceAvPlayerAudio { + u16 channel_count; + u8 reserved1[2]; + u32 sample_rate; + u32 size; + u8 language_code[4]; +}; + +struct SceAvPlayerVideo { + u32 width; + u32 height; + f32 aspect_ratio; + u8 language_code[4]; +}; + +struct SceAvPlayerTextPosition { + u16 top; + u16 left; + u16 bottom; + u16 right; +}; + +struct SceAvPlayerTimedText { + u8 language_code[4]; + u16 text_size; + u16 font_size; + SceAvPlayerTextPosition position; +}; + +union SceAvPlayerStreamDetails { + u8 reserved[16]; + SceAvPlayerAudio audio; + SceAvPlayerVideo video; + SceAvPlayerTimedText subs; +}; + +struct SceAvPlayerFrameInfo { + u8* pData; + u8 reserved[4]; + u64 timestamp; + SceAvPlayerStreamDetails details; +}; + +struct SceAvPlayerStreamInfo { + u32 type; + u8 reserved[4]; + SceAvPlayerStreamDetails details; + u64 duration; + u64 start_time; +}; + +struct SceAvPlayerAudioEx { + u16 channel_count; + u8 reserved[2]; + u32 sample_rate; + u32 size; + u8 language_code[4]; + u8 reserved1[64]; +}; + +struct SceAvPlayerVideoEx { + u32 width; + u32 height; + f32 aspect_ratio; + u8 language_code[4]; + u32 framerate; + u32 crop_left_offset; + u32 crop_right_offset; + u32 crop_top_offset; + u32 crop_bottom_offset; + u32 pitch; + u8 luma_bit_depth; + u8 chroma_bit_depth; + bool video_full_range_flag; + u8 reserved1[37]; +}; + +struct SceAvPlayerTimedTextEx { + u8 language_code[4]; + u8 reserved[12]; + u8 reserved1[64]; +}; + +union SceAvPlayerStreamDetailsEx { + SceAvPlayerAudioEx audio; + SceAvPlayerVideoEx video; + SceAvPlayerTimedTextEx subs; + u8 reserved1[80]; +}; + +struct SceAvPlayerFrameInfoEx { + void* pData; + u8 reserved[4]; + u64 timestamp; + SceAvPlayerStreamDetailsEx details; +}; + +typedef void* PS4_SYSV_ABI (*SceAvPlayerAllocate)(void* p, u32 align, u32 size); +typedef void PS4_SYSV_ABI (*SceAvPlayerDeallocate)(void* p, void* mem); +typedef void* PS4_SYSV_ABI (*SceAvPlayerAllocateTexture)(void* p, u32 align, u32 size); +typedef void PS4_SYSV_ABI (*SceAvPlayerDeallocateTexture)(void* p, void* mem); + +struct SceAvPlayerMemAllocator { + void* object_ptr; + SceAvPlayerAllocate allocate; + SceAvPlayerDeallocate deallocate; + SceAvPlayerAllocateTexture allocate_texture; + SceAvPlayerDeallocateTexture deallocate_texture; +}; + +typedef s32 PS4_SYSV_ABI (*SceAvPlayerOpenFile)(void* p, const char* name); +typedef s32 PS4_SYSV_ABI (*SceAvPlayerCloseFile)(void* p); +typedef s32 PS4_SYSV_ABI (*SceAvPlayerReadOffsetFile)(void* p, u8* buf, u64 pos, u32 len); +typedef u64 PS4_SYSV_ABI (*SceAvPlayerSizeFile)(void* p); + +struct SceAvPlayerFileReplacement { + void* object_ptr; + SceAvPlayerOpenFile open; + SceAvPlayerCloseFile close; + SceAvPlayerReadOffsetFile readOffset; + SceAvPlayerSizeFile size; +}; + +typedef void PS4_SYSV_ABI (*SceAvPlayerEventCallback)(void* p, s32 event, s32 src_id, void* data); + +struct SceAvPlayerEventReplacement { + void* object_ptr; + SceAvPlayerEventCallback event_callback; +}; + +enum SceAvPlayerDebuglevels { + SCE_AVPLAYER_DBG_NONE, + SCE_AVPLAYER_DBG_INFO, + SCE_AVPLAYER_DBG_WARNINGS, + SCE_AVPLAYER_DBG_ALL +}; + +struct SceAvPlayerInitData { + SceAvPlayerMemAllocator memory_replacement; + SceAvPlayerFileReplacement file_replacement; + SceAvPlayerEventReplacement event_replacement; + SceAvPlayerDebuglevels debug_level; + u32 base_priority; + s32 num_output_video_framebuffers; + bool auto_start; + u8 reserved[3]; + const char* default_language; +}; + +struct SceAvPlayerInitDataEx { + size_t this_size; + SceAvPlayerMemAllocator memory_replacement; + SceAvPlayerFileReplacement file_replacement; + SceAvPlayerEventReplacement event_replacement; + const char* default_language; + SceAvPlayerDebuglevels debug_level; + u32 audio_decoder_priority; + u32 audio_decoder_affinity; + u32 video_decoder_priority; + u32 video_decoder_affinity; + u32 demuxer_priority; + u32 demuxer_affinity; + u32 controller_priority; + u32 controller_affinity; + u32 http_streaming_priority; + u32 http_streaming_affinity; + u32 file_streaming_priority; + u32 file_streaming_affinity; + s32 num_output_video_framebuffers; + bool auto_start; + u8 reserved[3]; +}; + +enum SceAvPlayerStreamType { + SCE_AVPLAYER_VIDEO, + SCE_AVPLAYER_AUDIO, + SCE_AVPLAYER_TIMEDTEXT, + SCE_AVPLAYER_UNKNOWN +}; + +enum SceAvPlayerVideoDecoderType { + SCE_AVPLAYER_VIDEO_DECODER_TYPE_DEFAULT = 0, + SCE_AVPLAYER_VIDEO_DECODER_TYPE_RESERVED1, + SCE_AVPLAYER_VIDEO_DECODER_TYPE_SOFTWARE, + SCE_AVPLAYER_VIDEO_DECODER_TYPE_SOFTWARE2 +}; + +enum SceAvPlayerAudioDecoderType { + SCE_AVPLAYER_AUDIO_DECODER_TYPE_DEFAULT = 0, + SCE_AVPLAYER_AUDIO_DECODER_TYPE_RESERVED1, + SCE_AVPLAYER_AUDIO_DECODER_TYPE_RESERVED2 +}; + +struct SceAvPlayerDecoderInit { + union { + SceAvPlayerVideoDecoderType video_type; + SceAvPlayerAudioDecoderType audio_type; + u8 reserved[4]; + } decoderType; + union { + struct { + s32 cpu_affinity_mask; + s32 cpu_thread_priority; + u8 decode_pipeline_depth; + u8 compute_pipe_id; + u8 compute_queue_id; + u8 enable_interlaced; + u8 reserved[16]; + } avcSw2; + struct { + u8 audio_channel_order; + u8 reserved[27]; + } aac; + u8 reserved[28]; + } decoderParams; +}; + +struct SceAvPlayerHTTPCtx { + u32 http_context_id; + u32 ssl_context_id; +}; + +struct SceAvPlayerPostInitData { + u32 demux_video_buffer_size; + SceAvPlayerDecoderInit video_decoder_init; + SceAvPlayerDecoderInit audio_decoder_init; + SceAvPlayerHTTPCtx http_context; + u8 reserved[56]; +}; + +enum SceAvPlayerAvSyncMode { + SCE_AVPLAYER_AV_SYNC_MODE_DEFAULT = 0, + SCE_AVPLAYER_AV_SYNC_MODE_NONE +}; + +typedef int PS4_SYSV_ABI (*SceAvPlayerLogCallback)(void* p, const char* format, va_list args); + +enum SceAvPlayerEvents { + SCE_AVPLAYER_STATE_STOP = 0x01, + SCE_AVPLAYER_STATE_READY = 0x02, + SCE_AVPLAYER_STATE_PLAY = 0x03, + SCE_AVPLAYER_STATE_PAUSE = 0x04, + SCE_AVPLAYER_STATE_BUFFERING = 0x05, + SCE_AVPLAYER_TIMED_TEXT_DELIVERY = 0x10, + SCE_AVPLAYER_WARNING_ID = 0x20, + SCE_AVPLAYER_ENCRYPTION = 0x30, + SCE_AVPLAYER_DRM_ERROR = 0x40 +}; void RegisterlibSceAvPlayer(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::AvPlayer \ No newline at end of file + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_common.cpp b/src/core/libraries/avplayer/avplayer_common.cpp new file mode 100644 index 00000000..3536c030 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_common.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "avplayer.h" +#include "avplayer_common.h" + +#include // std::equal +#include // std::tolower +#include // std::string_view + +namespace Libraries::AvPlayer { + +using namespace Kernel; + +Kernel::ScePthreadMutex CreateMutex(int type, const char* name) { + ScePthreadMutexattr attr{}; + ScePthreadMutex mutex{}; + if (scePthreadMutexattrInit(&attr) == 0) { + if (scePthreadMutexattrSettype(&attr, type) == 0) { + if (scePthreadMutexInit(&mutex, &attr, name) != 0) { + if (mutex != nullptr) { + scePthreadMutexDestroy(&mutex); + } + return nullptr; + } + } + } + if (attr != nullptr) { + scePthreadMutexattrDestroy(&attr); + } + return mutex; +} + +ScePthread CreateThread(Kernel::PthreadEntryFunc func, const ThreadParameters& params) { + ScePthreadAttr attr; + if (scePthreadAttrInit(&attr) != 0) { + return nullptr; + } + if (scePthreadAttrSetinheritsched(&attr, 0) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + + SceKernelSchedParam param{.sched_priority = static_cast(params.priority)}; + if (scePthreadAttrSetschedparam(&attr, ¶m) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + if (scePthreadAttrSetstacksize(&attr, std::min(params.stack_size, 0x4000u)) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + if (scePthreadAttrSetdetachstate(&attr, 0) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + if (params.affinity > 0) { + if (scePthreadAttrSetaffinity(&attr, params.affinity) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + } + + ScePthread thread{}; + if (scePthreadCreate(&thread, &attr, func, params.p_user_data, params.thread_name) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + + scePthreadAttrDestroy(&attr); + return thread; +} + +static bool ichar_equals(char a, char b) { + return std::tolower(static_cast(a)) == + std::tolower(static_cast(b)); +} + +static bool iequals(std::string_view l, std::string_view r) { + return std::ranges::equal(l, r, ichar_equals); +} + +SceAvPlayerSourceType GetSourceType(std::string_view path) { + if (path.empty()) { + return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN; + } + + std::string_view name = path; + if (path.find("://") != std::string_view::npos) { + // This path is a URI. Strip HTTP parameters from it. + // schema://server.domain/path/file.ext/and/beyond?param=value#paragraph -> + // -> schema://server.domain/path/to/file.ext/and/beyond + name = path.substr(0, path.find_first_of("?#")); + if (name.empty()) { + return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN; + } + } + + // schema://server.domain/path/to/file.ext/and/beyond -> .ext/and/beyond + auto ext = name.substr(name.rfind('.')); + if (ext.empty()) { + return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN; + } + + // .ext/and/beyond -> .ext + ext = ext.substr(0, ext.find('/')); + + if (iequals(ext, ".mp4") || iequals(ext, ".m4v") || iequals(ext, ".m3d") || + iequals(ext, ".m4a") || iequals(ext, ".mov")) { + return SCE_AVPLAYER_SOURCE_TYPE_FILE_MP4; + } + + if (iequals(ext, ".m3u8")) { + return SCE_AVPLAYER_SOURCE_TYPE_HLS; + } + + return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN; +} + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_common.h b/src/core/libraries/avplayer/avplayer_common.h new file mode 100644 index 00000000..5faf2388 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_common.h @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/libraries/kernel/thread_management.h" + +#include +#include +#include + +#define AVPLAYER_IS_ERROR(x) ((x) < 0) + +namespace Libraries::AvPlayer { + +enum class AvState { + Initial, + AddingSource, + Ready, + Play, + Stop, + EndOfFile, + Pause, + C0x08, + Jump, + TrickMode, + C0x0B, + Buffering, + Starting, + C0x0E, + C0x0F, + C0x10, + Error, +}; + +enum class AvEventType { + ChangeFlowState = 21, + WarningId = 22, + RevertState = 30, + AddSource = 40, + Error = 255, +}; + +struct ThreadPriorities { + u32 audio_decoder_priority; + u32 audio_decoder_affinity; + u32 video_decoder_priority; + u32 video_decoder_affinity; + u32 demuxer_priority; + u32 demuxer_affinity; + u32 controller_priority; + u32 controller_affinity; + // u32 http_streaming_priority; + // u32 http_streaming_affinity; + // u32 file_streaming_priority; + // u32 file_streaming_affinity; + // u32 maxPriority; + // u32 maxAffinity; +}; + +union AvPlayerEventData { + u32 num_frames; // 20 + AvState state; // AvEventType::ChangeFlowState + s32 error; // AvEventType::WarningId + u32 attempt; // AvEventType::AddSource +}; + +struct AvPlayerEvent { + AvEventType event; + AvPlayerEventData payload; +}; + +Kernel::ScePthreadMutex CreateMutex(int type, const char* name); + +class PthreadMutex { +public: + using ScePthreadMutex = Kernel::ScePthreadMutex; + + PthreadMutex() = default; + + PthreadMutex(const PthreadMutex&) = delete; + PthreadMutex& operator=(const PthreadMutex&) = delete; + + PthreadMutex(PthreadMutex&& r) : m_mutex(r.m_mutex) { + r.m_mutex = nullptr; + } + PthreadMutex& operator=(PthreadMutex&& r) { + std::swap(m_mutex, r.m_mutex); + return *this; + } + + PthreadMutex(int type, const char* name) : m_mutex(CreateMutex(type, name)) {} + ~PthreadMutex() { + if (m_mutex != nullptr) { + Kernel::scePthreadMutexDestroy(&m_mutex); + } + } + + operator ScePthreadMutex() { + return m_mutex; + } + + int Lock() { + return Kernel::scePthreadMutexLock(&m_mutex); + } + + int Unlock() { + return Kernel::scePthreadMutexUnlock(&m_mutex); + } + + // implement BasicLockable to use std::lock_guard + // NOLINTNEXTLINE(readability-identifier-naming) + void lock() { + ASSERT_MSG(Lock() >= 0, "Could not lock the mutex"); + } + + // NOLINTNEXTLINE(readability-identifier-naming) + void unlock() { + ASSERT_MSG(Unlock() >= 0, "Could not unlock the mutex"); + } + + operator bool() { + return m_mutex != nullptr; + } + +private: + ScePthreadMutex m_mutex{}; +}; + +template +class AvPlayerQueue { +public: + AvPlayerQueue() : m_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayer0StlHandler") {} + + size_t Size() { + return m_queue.size(); + } + + void Push(T&& value) { + std::lock_guard guard(m_mutex); + m_queue.emplace(std::forward(value)); + } + + std::optional Pop() { + if (Size() == 0) { + return std::nullopt; + } + std::lock_guard guard(m_mutex); + auto result = std::move(m_queue.front()); + m_queue.pop(); + return result; + } + + void Clear() { + std::lock_guard guard(m_mutex); + m_queue = {}; + } + +private: + PthreadMutex m_mutex{}; + std::queue m_queue{}; +}; + +struct ThreadParameters { + void* p_user_data; + const char* thread_name; + u32 stack_size; + u32 priority; + u32 affinity; +}; + +Kernel::ScePthread CreateThread(Kernel::PthreadEntryFunc func, const ThreadParameters& params); +SceAvPlayerSourceType GetSourceType(std::string_view path); + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_data_streamer.h b/src/core/libraries/avplayer/avplayer_data_streamer.h new file mode 100644 index 00000000..04097bb4 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_data_streamer.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" + +#include "common/types.h" + +struct AVIOContext; + +namespace Libraries::AvPlayer { + +class IDataStreamer { +public: + virtual ~IDataStreamer() = default; + virtual AVIOContext* GetContext() = 0; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.cpp b/src/core/libraries/avplayer/avplayer_file_streamer.cpp new file mode 100644 index 00000000..1c54c454 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_file_streamer.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "avplayer_file_streamer.h" + +#include "avplayer_common.h" + +#include + +extern "C" { +#include +#include +} + +#define AVPLAYER_AVIO_BUFFER_SIZE 4096 + +namespace Libraries::AvPlayer { + +AvPlayerFileStreamer::AvPlayerFileStreamer(SceAvPlayerFileReplacement& file_replacement, + std::string_view path) + : m_file_replacement(file_replacement) { + Init(path); +} + +AvPlayerFileStreamer::~AvPlayerFileStreamer() { + if (m_avio_context != nullptr) { + avio_context_free(&m_avio_context); + } + if (m_avio_buffer != nullptr) { + av_free(m_avio_buffer); + } + if (m_file_replacement.close != nullptr && m_fd >= 0) { + const auto close = m_file_replacement.close; + const auto ptr = m_file_replacement.object_ptr; + close(ptr); + } +} + +s32 AvPlayerFileStreamer::Init(std::string_view path) { + const auto ptr = m_file_replacement.object_ptr; + m_fd = m_file_replacement.open(ptr, path.data()); + if (m_fd < 0) { + return -1; + } + m_file_size = m_file_replacement.size(ptr); + m_avio_buffer = reinterpret_cast(av_malloc(AVPLAYER_AVIO_BUFFER_SIZE)); + m_avio_context = + avio_alloc_context(m_avio_buffer, AVPLAYER_AVIO_BUFFER_SIZE, 0, this, + &AvPlayerFileStreamer::ReadPacket, nullptr, &AvPlayerFileStreamer::Seek); + return 0; +} + +s32 AvPlayerFileStreamer::ReadPacket(void* opaque, u8* buffer, s32 size) { + const auto self = reinterpret_cast(opaque); + if (self->m_position >= self->m_file_size) { + return AVERROR_EOF; + } + if (self->m_position + size > self->m_file_size) { + size = self->m_file_size - self->m_position; + } + const auto read_offset = self->m_file_replacement.readOffset; + const auto ptr = self->m_file_replacement.object_ptr; + const auto bytes_read = read_offset(ptr, buffer, self->m_position, size); + if (size != 0 && bytes_read == 0) { + return AVERROR_EOF; + } + self->m_position += bytes_read; + return bytes_read; +} + +s64 AvPlayerFileStreamer::Seek(void* opaque, s64 offset, int whence) { + const auto self = reinterpret_cast(opaque); + if (whence & AVSEEK_SIZE) { + return self->m_file_size; + } + + if (whence == SEEK_CUR) { + self->m_position = + std::min(u64(std::max(0ll, s64(self->m_position) + offset)), self->m_file_size); + return self->m_position; + } else if (whence == SEEK_SET) { + self->m_position = std::min(u64(std::max(0ll, offset)), self->m_file_size); + return self->m_position; + } else if (whence == SEEK_END) { + self->m_position = + std::min(u64(std::max(0ll, s64(self->m_file_size) + offset)), self->m_file_size); + return self->m_position; + } + + return -1; +} + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.h b/src/core/libraries/avplayer/avplayer_file_streamer.h new file mode 100644 index 00000000..9f1442f9 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_file_streamer.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" +#include "avplayer_data_streamer.h" + +#include +#include + +struct AVIOContext; + +namespace Libraries::AvPlayer { + +class AvPlayerFileStreamer : public IDataStreamer { +public: + AvPlayerFileStreamer(SceAvPlayerFileReplacement& file_replacement, std::string_view path); + ~AvPlayerFileStreamer(); + + AVIOContext* GetContext() override { + return m_avio_context; + } + +private: + s32 Init(std::string_view path); + + static s32 ReadPacket(void* opaque, u8* buffer, s32 size); + static s64 Seek(void* opaque, s64 buffer, int whence); + + SceAvPlayerFileReplacement m_file_replacement; + + int m_fd = -1; + u64 m_position{}; + u64 m_file_size{}; + u8* m_avio_buffer{}; + AVIOContext* m_avio_context{}; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp new file mode 100644 index 00000000..58793da7 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -0,0 +1,186 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "avplayer_common.h" +#include "avplayer_file_streamer.h" +#include "avplayer_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/kernel/libkernel.h" + +using namespace Libraries::Kernel; + +namespace Libraries::AvPlayer { + +void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) { + const auto* const self = reinterpret_cast(handle); + const auto allocate = self->m_init_data_original.memory_replacement.allocate; + const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; + return allocate(ptr, alignment, size); +} + +void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) { + const auto* const self = reinterpret_cast(handle); + const auto deallocate = self->m_init_data_original.memory_replacement.deallocate; + const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; + return deallocate(ptr, memory); +} + +void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) { + const auto* const self = reinterpret_cast(handle); + const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture; + const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; + return allocate(ptr, alignment, size); +} + +void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) { + const auto* const self = reinterpret_cast(handle); + const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture; + const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; + return deallocate(ptr, memory); +} + +int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { + auto const self = reinterpret_cast(handle); + std::lock_guard guard(self->m_file_io_mutex); + + const auto open = self->m_init_data_original.file_replacement.open; + const auto ptr = self->m_init_data_original.file_replacement.object_ptr; + return open(ptr, filename); +} + +int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { + auto const self = reinterpret_cast(handle); + std::lock_guard guard(self->m_file_io_mutex); + + const auto close = self->m_init_data_original.file_replacement.close; + const auto ptr = self->m_init_data_original.file_replacement.object_ptr; + return close(ptr); +} + +int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) { + auto const self = reinterpret_cast(handle); + std::lock_guard guard(self->m_file_io_mutex); + + const auto read_offset = self->m_init_data_original.file_replacement.readOffset; + const auto ptr = self->m_init_data_original.file_replacement.object_ptr; + return read_offset(ptr, buffer, position, length); +} + +u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { + auto const self = reinterpret_cast(handle); + std::lock_guard guard(self->m_file_io_mutex); + + const auto size = self->m_init_data_original.file_replacement.size; + const auto ptr = self->m_init_data_original.file_replacement.object_ptr; + return size(ptr); +} + +AvPlayer::AvPlayer() : m_file_io_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayerFileIOLock") {} + +void AvPlayer::Init(const SceAvPlayerInitData& data, const ThreadPriorities& priorities) { + m_init_data = data; + m_init_data_original = data; + + m_init_data.memory_replacement.object_ptr = this; + m_init_data.memory_replacement.allocate = &AvPlayer::Allocate; + m_init_data.memory_replacement.deallocate = &AvPlayer::Deallocate; + m_init_data.memory_replacement.allocate_texture = &AvPlayer::AllocateTexture; + m_init_data.memory_replacement.deallocate_texture = &AvPlayer::DeallocateTexture; + if (data.file_replacement.open == nullptr || data.file_replacement.close == nullptr || + data.file_replacement.readOffset == nullptr || data.file_replacement.size == nullptr) { + m_init_data.file_replacement = {}; + } else { + m_init_data.file_replacement.object_ptr = this; + m_init_data.file_replacement.open = &AvPlayer::OpenFile; + m_init_data.file_replacement.close = &AvPlayer::CloseFile; + m_init_data.file_replacement.readOffset = &AvPlayer::ReadOffsetFile; + m_init_data.file_replacement.size = &AvPlayer::SizeFile; + } + + m_state = std::make_unique(m_init_data, priorities); +} + +s32 AvPlayer::PostInit(const SceAvPlayerPostInitData& data) { + m_post_init_data = data; + return ORBIS_OK; +} + +s32 AvPlayer::AddSource(std::string_view path) { + if (path.empty()) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + + if (AVPLAYER_IS_ERROR(m_state->AddSource(path, GetSourceType(path)))) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + + return ORBIS_OK; +} + +s32 AvPlayer::GetStreamCount() { + return m_state->GetStreamCount(); +} + +s32 AvPlayer::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { + if (AVPLAYER_IS_ERROR(m_state->GetStreamInfo(stream_index, info))) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + return ORBIS_OK; +} + +s32 AvPlayer::EnableStream(u32 stream_id) { + if (m_state == nullptr) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + return m_state->EnableStream(stream_id); +} + +s32 AvPlayer::Start() { + return m_state->Start(); +} + +bool AvPlayer::GetVideoData(SceAvPlayerFrameInfo& video_info) { + if (m_state == nullptr) { + return false; + } + return m_state->GetVideoData(video_info); +} + +bool AvPlayer::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { + if (m_state == nullptr) { + return false; + } + return m_state->GetVideoData(video_info); +} + +bool AvPlayer::GetAudioData(SceAvPlayerFrameInfo& audio_info) { + if (m_state == nullptr) { + return false; + } + return m_state->GetAudioData(audio_info); +} + +bool AvPlayer::IsActive() { + if (m_state == nullptr) { + return false; + } + return m_state->IsActive(); +} + +u64 AvPlayer::CurrentTime() { + if (m_state == nullptr) { + return 0; + } + return m_state->CurrentTime(); +} + +s32 AvPlayer::Stop() { + if (m_state == nullptr || !m_state->Stop()) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + return ORBIS_OK; +} + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_impl.h b/src/core/libraries/avplayer/avplayer_impl.h new file mode 100644 index 00000000..fe3abcd1 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_impl.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" +#include "avplayer_data_streamer.h" +#include "avplayer_state.h" + +#include "core/libraries/kernel/thread_management.h" + +extern "C" { +#include +#include +} + +#include +#include + +namespace Libraries::AvPlayer { + +class AvPlayer { +public: + // Memory Replacement + static void* PS4_SYSV_ABI Allocate(void* handle, u32 alignment, u32 size); + static void PS4_SYSV_ABI Deallocate(void* handle, void* memory); + static void* PS4_SYSV_ABI AllocateTexture(void* handle, u32 alignment, u32 size); + static void PS4_SYSV_ABI DeallocateTexture(void* handle, void* memory); + + // File Replacement + static int PS4_SYSV_ABI OpenFile(void* handle, const char* filename); + static int PS4_SYSV_ABI CloseFile(void* handle); + static int PS4_SYSV_ABI ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length); + static u64 PS4_SYSV_ABI SizeFile(void* handle); + + AvPlayer(); + + void Init(const SceAvPlayerInitData& data, const ThreadPriorities& priorities); + + s32 PostInit(const SceAvPlayerPostInitData& data); + s32 AddSource(std::string_view filename); + s32 GetStreamCount(); + s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info); + s32 EnableStream(u32 stream_id); + s32 Start(); + bool GetAudioData(SceAvPlayerFrameInfo& audio_info); + bool GetVideoData(SceAvPlayerFrameInfo& video_info); + bool GetVideoData(SceAvPlayerFrameInfoEx& video_info); + bool IsActive(); + u64 CurrentTime(); + s32 Stop(); + +private: + using ScePthreadMutex = Kernel::ScePthreadMutex; + + std::unique_ptr m_state{}; + SceAvPlayerInitData m_init_data{}; + SceAvPlayerInitData m_init_data_original{}; + SceAvPlayerPostInitData m_post_init_data{}; + PthreadMutex m_file_io_mutex{}; + + std::atomic_bool m_has_source{}; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp new file mode 100644 index 00000000..c1dc6d57 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -0,0 +1,658 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "avplayer_source.h" + +#include "avplayer_file_streamer.h" + +#include "common/singleton.h" +#include "core/file_sys/fs.h" +#include "core/libraries/kernel/time_management.h" + +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +namespace Libraries::AvPlayer { + +using namespace Kernel; + +AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state) : m_state(state) {} + +AvPlayerSource::~AvPlayerSource() { + if (!m_video_frame_storage.empty()) { + m_memory_replacement.deallocate(m_memory_replacement.object_ptr, + m_video_frame_storage.data()); + } + if (!m_audio_frame_storage.empty()) { + m_memory_replacement.deallocate(m_memory_replacement.object_ptr, + m_audio_frame_storage.data()); + } + Stop(); +} + +s32 AvPlayerSource::Init(std::string_view path, SceAvPlayerMemAllocator& memory_replacement, + SceAvPlayerFileReplacement& file_replacement, ThreadPriorities& priorities, + SceAvPlayerSourceType source_type) { + if (m_avformat_context != nullptr) { + return -1; + } + + m_priorities = priorities; + m_memory_replacement = memory_replacement; + + m_avformat_context = avformat_alloc_context(); + if (m_avformat_context == nullptr) { + return -1; + } + if (file_replacement.open != nullptr) { + m_up_data_streamer = std::make_unique(file_replacement, path); + m_avformat_context->pb = m_up_data_streamer->GetContext(); + if (avformat_open_input(&m_avformat_context, nullptr, nullptr, nullptr) < 0) { + return -1; + } + } else { + const auto mnt = Common::Singleton::Instance(); + const auto filepath = mnt->GetHostPath(path); + if (AVPLAYER_IS_ERROR(avformat_open_input(&m_avformat_context, filepath.string().c_str(), + nullptr, nullptr))) { + return -1; + } + } + + return 0; +} + +bool AvPlayerSource::FindStreamInfo() { + if (m_avformat_context == nullptr) { + return false; + } + if (m_avformat_context->nb_streams > 0) { + return true; + } + return avformat_find_stream_info(m_avformat_context, nullptr) == 0; +} + +s32 AvPlayerSource::GetStreamCount() { + if (m_avformat_context == nullptr) { + return -1; + } + LOG_DEBUG(Lib_AvPlayer, "num streams: {}", m_avformat_context->nb_streams); + return m_avformat_context->nb_streams; +} + +static s32 CodecTypeToStreamType(AVMediaType codec_type) { + switch (codec_type) { + case AVMediaType::AVMEDIA_TYPE_VIDEO: + return SCE_AVPLAYER_VIDEO; + case AVMediaType::AVMEDIA_TYPE_AUDIO: + return SCE_AVPLAYER_AUDIO; + case AVMediaType::AVMEDIA_TYPE_SUBTITLE: + return SCE_AVPLAYER_TIMEDTEXT; + default: + LOG_ERROR(Lib_AvPlayer, "Unexpected AVMediaType {}", magic_enum::enum_name(codec_type)); + return -1; + } +} + +static f32 AVRationalToF32(AVRational rational) { + return f32(rational.num) / rational.den; +} + +s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { + info = {}; + if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) { + return -1; + } + const auto p_stream = m_avformat_context->streams[stream_index]; + if (p_stream == nullptr || p_stream->codecpar == nullptr) { + return -1; + } + info.type = CodecTypeToStreamType(p_stream->codecpar->codec_type); + info.start_time = p_stream->start_time; + info.duration = p_stream->duration; + const auto p_lang_node = av_dict_get(p_stream->metadata, "language", nullptr, 0); + if (p_lang_node == nullptr) { + return -1; + } + LOG_DEBUG(Lib_AvPlayer, "Stream {} language = {}", stream_index, p_lang_node->value); + switch (info.type) { + case SCE_AVPLAYER_VIDEO: + LOG_DEBUG(Lib_AvPlayer, "Stream {} is a video stream.", stream_index); + info.details.video.aspect_ratio = AVRationalToF32(p_stream->codecpar->sample_aspect_ratio); + info.details.video.width = p_stream->codecpar->width; + info.details.video.height = p_stream->codecpar->height; + std::memcpy(info.details.video.language_code, p_lang_node->value, + std::min(strlen(p_lang_node->value), 3ull)); + break; + case SCE_AVPLAYER_AUDIO: + LOG_DEBUG(Lib_AvPlayer, "Stream {} is an audio stream.", stream_index); + info.details.audio.channel_count = p_stream->codecpar->ch_layout.nb_channels; + info.details.audio.sample_rate = p_stream->codecpar->sample_rate; + info.details.audio.size = 0; // sceAvPlayerGetStreamInfo() is expected to set this to 0 + std::memcpy(info.details.audio.language_code, p_lang_node->value, + std::min(strlen(p_lang_node->value), 3ull)); + break; + case SCE_AVPLAYER_TIMEDTEXT: + LOG_WARNING(Lib_AvPlayer, "Stream {} is a timedtext stream.", stream_index); + info.details.subs.font_size = 12; + info.details.subs.text_size = 12; + std::memcpy(info.details.subs.language_code, p_lang_node->value, + std::min(strlen(p_lang_node->value), 3ull)); + break; + default: + return -1; + } + return 0; +} + +static AVPixelFormat GetPreferredVideoFormat(AVCodecContext* s, const AVPixelFormat* fmt) { + auto curr = fmt; + while (*curr != AV_PIX_FMT_NONE) { + LOG_TRACE(Lib_AvPlayer, "Supported format: {}", magic_enum::enum_name(*fmt)); + if (*curr == AV_PIX_FMT_NV12) { + return AV_PIX_FMT_NV12; + } + ++curr; + } + return AV_PIX_FMT_NONE; +} + +s32 AvPlayerSource::EnableStream(u32 stream_index) { + if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) { + return -1; + } + const auto stream = m_avformat_context->streams[stream_index]; + const auto decoder = avcodec_find_decoder(stream->codecpar->codec_id); + if (decoder == nullptr) { + return -1; + } + switch (stream->codecpar->codec_type) { + case AVMediaType::AVMEDIA_TYPE_VIDEO: { + m_video_stream_index = stream_index; + m_video_codec_context = + AVCodecContextPtr(avcodec_alloc_context3(decoder), &ReleaseAVCodecContext); + if (avcodec_parameters_to_context(m_video_codec_context.get(), stream->codecpar) < 0) { + return -1; + } + if (avcodec_open2(m_video_codec_context.get(), decoder, nullptr) < 0) { + return -1; + } + LOG_INFO(Lib_AvPlayer, "Video stream {} enabled", stream_index); + break; + } + case AVMediaType::AVMEDIA_TYPE_AUDIO: { + m_audio_stream_index = stream_index; + m_audio_codec_context = + AVCodecContextPtr(avcodec_alloc_context3(decoder), &ReleaseAVCodecContext); + if (avcodec_parameters_to_context(m_audio_codec_context.get(), stream->codecpar) < 0) { + return -1; + } + if (avcodec_open2(m_audio_codec_context.get(), decoder, nullptr) < 0) { + return -1; + } + LOG_INFO(Lib_AvPlayer, "Audio stream {} enabled", stream_index); + break; + } + default: + LOG_WARNING(Lib_AvPlayer, "Unknown stream type {} for stream {}", + magic_enum::enum_name(stream->codecpar->codec_type), stream_index); + break; + } + return 0; +} + +u8* AvPlayerSource::GetVideoBuffer(AVFrame* frame) { + const auto size = (frame->width * frame->height * 3) / 2; + if (m_video_frame_storage.size() < size) { + if (!m_video_frame_storage.empty()) { + m_memory_replacement.deallocate(m_memory_replacement.object_ptr, + m_video_frame_storage.data()); + } + const auto ptr = reinterpret_cast( + m_memory_replacement.allocate(m_memory_replacement.object_ptr, frame->width, size)); + m_video_frame_storage = std::span(ptr, size); + } + return m_video_frame_storage.data(); +} + +u8* AvPlayerSource::GetAudioBuffer(AVFrame* frame) { + const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(u16); + if (m_audio_frame_storage.size() < size) { + if (!m_audio_frame_storage.empty()) { + m_memory_replacement.deallocate(m_memory_replacement.object_ptr, + m_audio_frame_storage.data()); + } + const auto ptr = reinterpret_cast( + m_memory_replacement.allocate(m_memory_replacement.object_ptr, 0x40, size)); + m_audio_frame_storage = std::span(ptr, size); + } + return m_audio_frame_storage.data(); +} + +void AvPlayerSource::SetLooping(bool is_looping) { + m_is_looping = is_looping; +} + +std::optional AvPlayerSource::HasFrames(u32 num_frames) { + return m_video_frames.Size() > num_frames; +} + +s32 AvPlayerSource::Start() { + if (m_audio_codec_context == nullptr && m_video_codec_context == nullptr) { + return -1; + } + { + ThreadParameters demuxer_params{ + .p_user_data = this, + .thread_name = "AvPlayer_Demuxer", + .stack_size = 0x4000, + .priority = m_priorities.demuxer_priority, + .affinity = m_priorities.demuxer_affinity, + }; + m_demuxer_thread = CreateThread(&DemuxerThread, demuxer_params); + if (m_demuxer_thread == nullptr) { + return -1; + } + } + if (m_video_codec_context != nullptr) { + ThreadParameters video_decoder_params{ + .p_user_data = this, + .thread_name = "AvPlayer_VideoDecoder", + .stack_size = 0x4000, + .priority = m_priorities.video_decoder_priority, + .affinity = m_priorities.video_decoder_affinity, + }; + m_video_decoder_thread = CreateThread(&VideoDecoderThread, video_decoder_params); + if (m_video_decoder_thread == nullptr) { + return -1; + } + } + if (m_audio_codec_context != nullptr) { + ThreadParameters audio_decoder_params{ + .p_user_data = this, + .thread_name = "AvPlayer_AudioDecoder", + .stack_size = 0x4000, + .priority = m_priorities.audio_decoder_priority, + .affinity = m_priorities.audio_decoder_affinity, + }; + m_audio_decoder_thread = CreateThread(&AudioDecoderThread, audio_decoder_params); + if (m_audio_decoder_thread == nullptr) { + return -1; + } + } + m_start_time = std::chrono::high_resolution_clock::now(); + return 0; +} + +bool AvPlayerSource::Stop() { + m_is_stop = true; + + void* res = nullptr; + if (m_video_decoder_thread != nullptr) { + scePthreadJoin(m_video_decoder_thread, &res); + } + if (m_audio_decoder_thread != nullptr) { + scePthreadJoin(m_audio_decoder_thread, &res); + } + if (m_demuxer_thread != nullptr) { + scePthreadJoin(m_demuxer_thread, &res); + } + return true; +} + +bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfo& video_info) { + SceAvPlayerFrameInfoEx info{}; + if (!GetVideoData(info)) { + return false; + } + video_info = {}; + video_info.timestamp = u64(info.timestamp); + video_info.pData = reinterpret_cast(info.pData); + video_info.details.video.aspect_ratio = info.details.video.aspect_ratio; + video_info.details.video.width = info.details.video.width; + video_info.details.video.height = info.details.video.height; + return true; +} + +static void CopyNV12Data(u8* dst, const AVFrame& src) { + std::memcpy(dst, src.data[0], src.width * src.height); + std::memcpy(dst + src.width * src.height, src.data[1], (src.width * src.height) / 2); +} + +bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { + while (m_video_frames.Size() == 0 && !m_is_eof) { + sceKernelUsleep(5000); + } + + auto frame = m_video_frames.Pop(); + if (!frame.has_value()) { + return false; + } + + auto& up_frame = *frame; + + const auto pkt_dts = u64(up_frame->pkt_dts) * 1000; + const auto stream = m_avformat_context->streams[m_video_stream_index.value()]; + const auto time_base = stream->time_base; + const auto den = time_base.den; + const auto num = time_base.num; + const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts; + + { + using namespace std::chrono; + auto elapsed_time = + duration_cast(high_resolution_clock::now() - m_start_time).count(); + while (elapsed_time < timestamp) { + sceKernelUsleep((timestamp - elapsed_time) * 1000); + elapsed_time = + duration_cast(high_resolution_clock::now() - m_start_time).count(); + } + } + + auto buffer = GetVideoBuffer(up_frame.get()); + + CopyNV12Data(buffer, *up_frame); + + video_info = {}; + video_info.timestamp = timestamp; + video_info.pData = buffer; + video_info.details.video.width = up_frame->width; + video_info.details.video.height = up_frame->height; + video_info.details.video.aspect_ratio = AVRationalToF32(up_frame->sample_aspect_ratio); + video_info.details.video.pitch = up_frame->linesize[0]; + video_info.details.video.luma_bit_depth = 8; + video_info.details.video.chroma_bit_depth = 8; + + m_last_video_timestamp = timestamp; + return true; +} + +bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { + while (m_audio_frames.Size() == 0 && !m_is_eof) { + sceKernelUsleep(5000); + } + + auto frame = m_audio_frames.Pop(); + if (!frame.has_value()) { + return false; + } + + const auto& up_frame = *frame; + + const auto pkt_dts = u64(up_frame->pkt_dts) * 1000; + const auto stream = m_avformat_context->streams[m_audio_stream_index.value()]; + const auto time_base = stream->time_base; + const auto den = time_base.den; + const auto num = time_base.num; + const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts; + + { + using namespace std::chrono; + auto elapsed_time = + duration_cast(high_resolution_clock::now() - m_start_time).count(); + while (elapsed_time < timestamp) { + sceKernelUsleep((timestamp - elapsed_time) * 1000); + elapsed_time = + duration_cast(high_resolution_clock::now() - m_start_time).count(); + } + } + + auto buffer = GetAudioBuffer(up_frame.get()); + const auto size = up_frame->ch_layout.nb_channels * up_frame->nb_samples * sizeof(u16); + std::memcpy(buffer, up_frame->data[0], size); + + audio_info = {}; + audio_info.timestamp = timestamp; + audio_info.pData = m_audio_frame_storage.data(); + audio_info.details.audio.size = u32(m_audio_frame_storage.size()); + audio_info.details.audio.channel_count = up_frame->ch_layout.nb_channels; + return true; +} + +u64 AvPlayerSource::CurrentTime() { + // using namespace std::chrono; + // return duration_cast(high_resolution_clock::now() - m_start_time).count(); + return m_last_video_timestamp; +} + +bool AvPlayerSource::IsActive() { + return !m_is_stop && (!m_is_eof || m_audio_packets.Size() != 0 || m_video_packets.Size() != 0 || + m_video_frames.Size() != 0 || m_audio_frames.Size() != 0); +} + +void AvPlayerSource::ReleaseAVPacket(AVPacket* packet) { + if (packet != nullptr) { + av_packet_free(&packet); + } +} + +void AvPlayerSource::ReleaseAVFrame(AVFrame* frame) { + if (frame != nullptr) { + av_frame_free(&frame); + } +} + +void AvPlayerSource::ReleaseAVCodecContext(AVCodecContext* context) { + if (context != nullptr) { + avcodec_free_context(&context); + } +} + +void AvPlayerSource::ReleaseSWRContext(SwrContext* context) { + if (context != nullptr) { + swr_free(&context); + } +} + +void AvPlayerSource::ReleaseSWSContext(SwsContext* context) { + if (context != nullptr) { + sws_freeContext(context); + } +} + +void* AvPlayerSource::DemuxerThread(void* opaque) { + LOG_TRACE(Lib_AvPlayer, "Demuxer Thread started"); + const auto self = reinterpret_cast(opaque); + if (!self->m_audio_stream_index.has_value() && !self->m_video_stream_index.has_value()) { + return nullptr; + } + + while (!self->m_is_stop) { + if (self->m_video_packets.Size() > 60) { + sceKernelUsleep(5000); + continue; + } + AVPacketPtr up_packet(av_packet_alloc(), &ReleaseAVPacket); + const auto res = av_read_frame(self->m_avformat_context, up_packet.get()); + if (res < 0) { + if (res == AVERROR_EOF) { + LOG_TRACE(Lib_AvPlayer, "EOF reached in demuxer"); + break; + } else { + LOG_ERROR(Lib_AvPlayer, "Could not read AV frame: error = {}", res); + self->m_state.OnError(); + scePthreadExit(0); + } + break; + } + if (up_packet->stream_index == self->m_video_stream_index) { + self->m_video_packets.Push(std::move(up_packet)); + } else if (up_packet->stream_index == self->m_audio_stream_index) { + self->m_audio_packets.Push(std::move(up_packet)); + } + } + + self->m_is_eof = true; + + void* res; + if (self->m_video_decoder_thread) { + scePthreadJoin(self->m_video_decoder_thread, &res); + } + if (self->m_audio_decoder_thread) { + scePthreadJoin(self->m_audio_decoder_thread, &res); + } + self->m_state.OnEOF(); + + LOG_TRACE(Lib_AvPlayer, "Demuxer Thread exited normaly"); + scePthreadExit(0); +} + +AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& frame) { + auto nv12_frame = AVFramePtr{av_frame_alloc(), &ReleaseAVFrame}; + nv12_frame->pts = frame.pts; + nv12_frame->pkt_dts = frame.pkt_dts; + nv12_frame->format = AV_PIX_FMT_NV12; + nv12_frame->width = frame.width; + nv12_frame->height = frame.height; + nv12_frame->sample_aspect_ratio = frame.sample_aspect_ratio; + + av_frame_get_buffer(nv12_frame.get(), 0); + + if (m_sws_context == nullptr) { + m_sws_context = + SWSContextPtr(sws_getContext(frame.width, frame.height, AVPixelFormat(frame.format), + frame.width, frame.height, AV_PIX_FMT_NV12, + SWS_FAST_BILINEAR, nullptr, nullptr, nullptr), + &ReleaseSWSContext); + } + const auto res = sws_scale(m_sws_context.get(), frame.data, frame.linesize, 0, frame.height, + nv12_frame->data, nv12_frame->linesize); + if (res < 0) { + LOG_ERROR(Lib_AvPlayer, "Could not convert to NV12: {}", av_err2str(res)); + return AVFramePtr{nullptr, &ReleaseAVFrame}; + } + return nv12_frame; +} + +void* AvPlayerSource::VideoDecoderThread(void* opaque) { + LOG_TRACE(Lib_AvPlayer, "Video Decoder Thread started"); + const auto self = reinterpret_cast(opaque); + + while ((!self->m_is_eof || self->m_video_packets.Size() != 0) && !self->m_is_stop) { + if (self->m_video_frames.Size() > 60 || self->m_video_packets.Size() == 0) { + sceKernelUsleep(5000); + continue; + } + const auto packet = self->m_video_packets.Pop(); + if (!packet.has_value()) { + continue; + } + auto res = avcodec_send_packet(self->m_video_codec_context.get(), packet->get()); + if (res < 0 && res != AVERROR(EAGAIN)) { + self->m_state.OnError(); + LOG_ERROR(Lib_AvPlayer, "Could not send packet to the video codec. Error = {}", + av_err2str(res)); + scePthreadExit(nullptr); + } + while (res >= 0) { + auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); + res = avcodec_receive_frame(self->m_video_codec_context.get(), up_frame.get()); + if (res < 0) { + if (res == AVERROR_EOF) { + LOG_TRACE(Lib_AvPlayer, "EOF reached in video decoder"); + scePthreadExit(nullptr); + } else if (res != AVERROR(EAGAIN)) { + LOG_ERROR(Lib_AvPlayer, + "Could not receive frame from the video codec. Error = {}", + av_err2str(res)); + self->m_state.OnError(); + scePthreadExit(nullptr); + } + } else { + LOG_TRACE(Lib_AvPlayer, "Producing Video Frame. Num Frames: {}", + self->m_video_frames.Size()); + if (up_frame->format != AV_PIX_FMT_NV12) { + self->m_video_frames.Push(self->ConvertVideoFrame(*up_frame)); + } else { + self->m_video_frames.Push(std::move(up_frame)); + } + } + } + } + + LOG_TRACE(Lib_AvPlayer, "Video Decoder Thread exited normaly"); + scePthreadExit(nullptr); +} + +AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& frame) { + auto pcm16_frame = AVFramePtr{av_frame_alloc(), &ReleaseAVFrame}; + pcm16_frame->pts = frame.pts; + pcm16_frame->pkt_dts = frame.pkt_dts; + pcm16_frame->format = AV_SAMPLE_FMT_S16; + pcm16_frame->ch_layout = frame.ch_layout; + pcm16_frame->sample_rate = frame.sample_rate; + + if (m_swr_context == nullptr) { + SwrContext* swr_context = nullptr; + AVChannelLayout in_ch_layout = frame.ch_layout; + AVChannelLayout out_ch_layout = frame.ch_layout; + swr_alloc_set_opts2(&swr_context, &out_ch_layout, AV_SAMPLE_FMT_S16, frame.sample_rate, + &in_ch_layout, AVSampleFormat(frame.format), frame.sample_rate, 0, + nullptr); + m_swr_context = SWRContextPtr(swr_context, &ReleaseSWRContext); + swr_init(m_swr_context.get()); + } + const auto res = swr_convert_frame(m_swr_context.get(), pcm16_frame.get(), &frame); + if (res < 0) { + LOG_ERROR(Lib_AvPlayer, "Could not convert to NV12: {}", av_err2str(res)); + return AVFramePtr{nullptr, &ReleaseAVFrame}; + } + return pcm16_frame; +} + +void* AvPlayerSource::AudioDecoderThread(void* opaque) { + LOG_TRACE(Lib_AvPlayer, "Audio Decoder Thread started"); + const auto self = reinterpret_cast(opaque); + + while ((!self->m_is_eof || self->m_audio_packets.Size() != 0) && !self->m_is_stop) { + if (self->m_audio_frames.Size() > 60 || self->m_audio_packets.Size() == 0) { + sceKernelUsleep(5000); + continue; + } + const auto packet = self->m_audio_packets.Pop(); + if (!packet.has_value()) { + continue; + } + auto res = avcodec_send_packet(self->m_audio_codec_context.get(), packet->get()); + if (res < 0 && res != AVERROR(EAGAIN)) { + self->m_state.OnError(); + LOG_ERROR(Lib_AvPlayer, "Could not send packet to the audio codec. Error = {}", + av_err2str(res)); + scePthreadExit(nullptr); + } + while (res >= 0) { + auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); + res = avcodec_receive_frame(self->m_audio_codec_context.get(), up_frame.get()); + if (res < 0) { + if (res == AVERROR_EOF) { + LOG_TRACE(Lib_AvPlayer, "EOF reached in audio decoder"); + scePthreadExit(nullptr); + } else if (res != AVERROR(EAGAIN)) { + self->m_state.OnError(); + LOG_ERROR(Lib_AvPlayer, + "Could not receive frame from the audio codec. Error = {}", + av_err2str(res)); + scePthreadExit(nullptr); + } + } else { + if (up_frame->format != AV_SAMPLE_FMT_S16) { + self->m_audio_frames.Push(self->ConvertAudioFrame(*up_frame)); + } else { + self->m_audio_frames.Push(std::move(up_frame)); + } + } + } + } + + LOG_TRACE(Lib_AvPlayer, "Audio Decoder Thread exited normaly"); + scePthreadExit(nullptr); +} + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h new file mode 100644 index 00000000..81f77a62 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -0,0 +1,169 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" +#include "avplayer_common.h" +#include "avplayer_data_streamer.h" + +#include "common/types.h" +#include "core/libraries/kernel/thread_management.h" + +#include +#include +#include +#include + +struct AVCodecContext; +struct AVFormatContext; +struct AVFrame; +struct AVIOContext; +struct AVPacket; +struct SwrContext; +struct SwsContext; + +namespace Libraries::AvPlayer { + +class AvPlayerStateCallback { +public: + virtual ~AvPlayerStateCallback() = default; + + virtual void OnWarning(u32 id) = 0; + virtual void OnError() = 0; + virtual void OnEOF() = 0; +}; + +class FrameBuffer { +public: + FrameBuffer(const SceAvPlayerMemAllocator& memory_replacement, u32 align, u32 size) noexcept + : m_memory_replacement(memory_replacement), + m_data(Allocate(memory_replacement, align, size), size) {} + + ~FrameBuffer() { + if (!m_data.empty()) { + Deallocate(m_memory_replacement, m_data.data()); + m_data = {}; + } + } + + FrameBuffer(const FrameBuffer&) noexcept = delete; + FrameBuffer& operator=(const FrameBuffer&) noexcept = delete; + + FrameBuffer(FrameBuffer&& r) noexcept + : m_memory_replacement(r.m_memory_replacement), m_data(r.m_data) { + r.m_data = {}; + }; + + FrameBuffer& operator=(FrameBuffer&& r) noexcept { + m_memory_replacement = r.m_memory_replacement; + std::swap(m_data, r.m_data); + return *this; + } + + u8* GetBuffer() const noexcept { + return m_data.data(); + } + +private: + static u8* Allocate(const SceAvPlayerMemAllocator& memory_replacement, u32 align, u32 size) { + return reinterpret_cast( + memory_replacement.allocate(memory_replacement.object_ptr, align, size)); + } + + static void Deallocate(const SceAvPlayerMemAllocator& memory_replacement, void* ptr) { + memory_replacement.deallocate(memory_replacement.object_ptr, ptr); + } + + SceAvPlayerMemAllocator m_memory_replacement; + std::span m_data; +}; + +class AvPlayerSource { +public: + AvPlayerSource(AvPlayerStateCallback& state); + ~AvPlayerSource(); + + s32 Init(std::string_view path, SceAvPlayerMemAllocator& memory_replacement, + SceAvPlayerFileReplacement& file_replacement, ThreadPriorities& priorities, + SceAvPlayerSourceType source_type); + + bool FindStreamInfo(); + s32 GetStreamCount(); + s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info); + s32 EnableStream(u32 stream_index); + void SetLooping(bool is_looping); + std::optional HasFrames(u32 num_frames); + s32 Start(); + bool Stop(); + bool GetAudioData(SceAvPlayerFrameInfo& audio_info); + bool GetVideoData(SceAvPlayerFrameInfo& video_info); + bool GetVideoData(SceAvPlayerFrameInfoEx& video_info); + u64 CurrentTime(); + bool IsActive(); + +private: + using ScePthread = Kernel::ScePthread; + + static void* PS4_SYSV_ABI DemuxerThread(void* opaque); + static void* PS4_SYSV_ABI VideoDecoderThread(void* opaque); + static void* PS4_SYSV_ABI AudioDecoderThread(void* opaque); + + static void ReleaseAVPacket(AVPacket* packet); + static void ReleaseAVFrame(AVFrame* frame); + static void ReleaseAVCodecContext(AVCodecContext* context); + static void ReleaseSWRContext(SwrContext* context); + static void ReleaseSWSContext(SwsContext* context); + + using AVPacketPtr = std::unique_ptr; + using AVFramePtr = std::unique_ptr; + using AVCodecContextPtr = std::unique_ptr; + using SWRContextPtr = std::unique_ptr; + using SWSContextPtr = std::unique_ptr; + + u8* GetVideoBuffer(AVFrame* frame); + u8* GetAudioBuffer(AVFrame* frame); + + AVFramePtr ConvertAudioFrame(const AVFrame& frame); + AVFramePtr ConvertVideoFrame(const AVFrame& frame); + + u64 m_last_video_timestamp{}; + + AvPlayerStateCallback& m_state; + + ThreadPriorities m_priorities; + SceAvPlayerMemAllocator m_memory_replacement; + + std::atomic_bool m_is_looping = false; + std::atomic_bool m_is_eof = false; + std::atomic_bool m_is_stop = false; + std::unique_ptr m_up_data_streamer; + + AVFormatContext* m_avformat_context{}; + + AvPlayerQueue m_audio_packets; + AvPlayerQueue m_video_packets; + + AvPlayerQueue m_audio_frames; + AvPlayerQueue m_video_frames; + + std::span m_video_frame_storage; + std::span m_audio_frame_storage; + + AVCodecContextPtr m_video_codec_context{nullptr, &ReleaseAVCodecContext}; + AVCodecContextPtr m_audio_codec_context{nullptr, &ReleaseAVCodecContext}; + + std::optional m_video_stream_index{}; + std::optional m_audio_stream_index{}; + + ScePthread m_demuxer_thread{}; + ScePthread m_video_decoder_thread{}; + ScePthread m_audio_decoder_thread{}; + + SWRContextPtr m_swr_context{nullptr, &ReleaseSWRContext}; + SWSContextPtr m_sws_context{nullptr, &ReleaseSWSContext}; + + std::chrono::high_resolution_clock::time_point m_start_time{}; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp new file mode 100644 index 00000000..3048819e --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -0,0 +1,481 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "avplayer_file_streamer.h" +#include "avplayer_source.h" +#include "avplayer_state.h" + +#include "core/libraries/error_codes.h" +#include "core/libraries/kernel/time_management.h" + +#include + +namespace Libraries::AvPlayer { + +using namespace Kernel; + +void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_id, s32 source_id, + void* event_data) { + auto const self = reinterpret_cast(opaque); + + if (event_id == SCE_AVPLAYER_STATE_READY) { + s32 video_stream_index = -1; + s32 audio_stream_index = -1; + s32 timedtext_stream_index = -1; + const s32 stream_count = self->GetStreamCount(); + if (AVPLAYER_IS_ERROR(stream_count)) { + return; + } + if (stream_count == 0) { + self->Stop(); + return; + } + for (u32 stream_index = 0; stream_index < stream_count; ++stream_index) { + SceAvPlayerStreamInfo info{}; + self->GetStreamInfo(stream_index, info); + + const std::string_view default_language( + reinterpret_cast(self->m_default_language)); + switch (info.type) { + case SCE_AVPLAYER_VIDEO: + if (video_stream_index == -1) { + if (default_language.empty()) { + video_stream_index = stream_index; + break; + } + if (default_language == + reinterpret_cast(info.details.video.language_code)) { + video_stream_index = stream_index; + } + } + break; + case SCE_AVPLAYER_AUDIO: + if (audio_stream_index == -1) { + if (default_language.empty()) { + audio_stream_index = stream_index; + break; + } + if (default_language == + reinterpret_cast(info.details.video.language_code)) { + audio_stream_index = stream_index; + } + } + break; + case SCE_AVPLAYER_TIMEDTEXT: + if (timedtext_stream_index == -1) { + if (default_language.empty()) { + timedtext_stream_index = stream_index; + break; + } + if (default_language == + reinterpret_cast(info.details.video.language_code)) { + timedtext_stream_index = stream_index; + } + } + break; + } + } + + if (video_stream_index != -1) { + self->EnableStream(video_stream_index); + } + if (audio_stream_index != -1) { + self->EnableStream(audio_stream_index); + } + if (timedtext_stream_index != -1) { + self->EnableStream(timedtext_stream_index); + } + self->Start(); + return; + } + + const auto callback = self->m_user_event_replacement.event_callback; + const auto ptr = self->m_user_event_replacement.object_ptr; + if (callback != nullptr) { + callback(ptr, event_id, 0, event_data); + } +} + +// Called inside GAME thread +AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data, + const ThreadPriorities& priorities) + : m_event_handler_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayerEventHandler"), + m_state_machine_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayerStateMachine") { + if (init_data.event_replacement.event_callback == nullptr || init_data.auto_start) { + m_auto_start = true; + m_event_replacement.event_callback = &AvPlayerState::AutoPlayEventCallback; + m_event_replacement.object_ptr = this; + } else { + m_event_replacement = init_data.event_replacement; + } + m_user_event_replacement = init_data.event_replacement; + + const auto& memory_replacement = init_data.memory_replacement; + if (memory_replacement.allocate != nullptr && memory_replacement.deallocate != nullptr && + memory_replacement.allocate_texture != nullptr && + memory_replacement.deallocate_texture != nullptr) { + m_memory_replacement = memory_replacement; + } + + if (init_data.event_replacement.event_callback != nullptr) { + m_event_replacement = init_data.event_replacement; + m_auto_start = init_data.auto_start; + } else { + m_auto_start = true; + } + + m_file_replacement = init_data.file_replacement; + m_thread_priorities = priorities; + + if (init_data.default_language != nullptr) { + std::memcpy(m_default_language, init_data.default_language, sizeof(m_default_language)); + } + SetState(AvState::Initial); + StartControllerThread(); +} + +AvPlayerState::~AvPlayerState() { + m_event_queue.Clear(); + if (m_up_source && m_current_state == AvState::Play) { + m_up_source->Stop(); + OnPlaybackStateChanged(AvState::Stop); + } +} + +// Called inside GAME thread +s32 AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType source_type) { + if (path.empty()) { + return -1; + } + + if (m_up_source) { + return -1; + } + + m_up_source = std::make_unique(*this); + if (AVPLAYER_IS_ERROR(m_up_source->Init(path, m_memory_replacement, m_file_replacement, + m_thread_priorities, source_type))) { + return -1; + } + AddSourceEvent(); + + return 0; +} + +// Called inside GAME thread +s32 AvPlayerState::GetStreamCount() { + return m_up_source->GetStreamCount(); +} + +// Called inside GAME thread +s32 AvPlayerState::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { + return m_up_source->GetStreamInfo(stream_index, info); +} + +// Called inside GAME thread +s32 AvPlayerState::Start() { + if (m_up_source == nullptr) { + return -1; + } + return m_up_source->Start(); +} + +void* AvPlayerState::AvControllerThread(void* p_user_data) { + AvPlayerState* self = reinterpret_cast(p_user_data); + while (self->m_quit.load() == 0) { + if (self->m_event_queue.Size() != 0) { + self->ProcessEvent(); + continue; + } + sceKernelUsleep(5000); + self->UpdateBufferingState(); + } + scePthreadExit(0); +} + +// Called inside GAME thread +void AvPlayerState::AddSourceEvent() { + SetState(AvState::AddingSource); + m_event_queue.Push(AvPlayerEvent{ + .event = AvEventType::AddSource, + }); +} + +// Called inside GAME thread +int AvPlayerState::StartControllerThread() { + m_quit.store(0); + + ThreadParameters params{ + .p_user_data = this, + .thread_name = "AvPlayer_Controller", + .stack_size = 0x4000, + .priority = m_thread_priority, + .affinity = m_thread_affinity, + }; + m_controller_thread = CreateThread(&AvControllerThread, params); + if (m_controller_thread == nullptr) { + return -1; + } + return 0; +} + +// Called inside GAME thread +s32 AvPlayerState::EnableStream(u32 stream_id) { + if (m_up_source == nullptr) { + return -1; + } + return m_up_source->EnableStream(stream_id); +} + +// Called inside GAME thread +bool AvPlayerState::Stop() { + if (m_up_source == nullptr) { + return false; + } + SetState(AvState::Stop); + OnPlaybackStateChanged(AvState::Stop); + return m_up_source->Stop(); +} + +bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfo& video_info) { + if (m_up_source == nullptr) { + return false; + } + return m_up_source->GetVideoData(video_info); +} + +bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { + if (m_up_source == nullptr) { + return false; + } + return m_up_source->GetVideoData(video_info); +} + +bool AvPlayerState::GetAudioData(SceAvPlayerFrameInfo& audio_info) { + if (m_up_source == nullptr) { + return false; + } + return m_up_source->GetAudioData(audio_info); +} + +bool AvPlayerState::IsActive() { + if (m_up_source == nullptr) { + return false; + } + return m_current_state != AvState::Stop && m_current_state != AvState::Error && + m_up_source->IsActive(); +} + +u64 AvPlayerState::CurrentTime() { + if (m_up_source == nullptr) { + return 0; + } + return m_up_source->CurrentTime(); +} + +void AvPlayerState::OnWarning(u32 id) { + EmitEvent(SCE_AVPLAYER_WARNING_ID, &id); +} + +void AvPlayerState::OnError() { + SetState(AvState::Error); + OnPlaybackStateChanged(AvState::Error); +} + +void AvPlayerState::OnEOF() { + Stop(); +} + +// Called inside CONTROLLER thread +void AvPlayerState::OnPlaybackStateChanged(AvState state) { + switch (state) { + case AvState::Ready: { + EmitEvent(SCE_AVPLAYER_STATE_READY); + break; + } + case AvState::Play: { + EmitEvent(SCE_AVPLAYER_STATE_PLAY); + break; + } + case AvState::Stop: { + EmitEvent(SCE_AVPLAYER_STATE_STOP); + break; + } + case AvState::Pause: { + EmitEvent(SCE_AVPLAYER_STATE_PAUSE); + break; + } + case AvState::Buffering: { + EmitEvent(SCE_AVPLAYER_STATE_BUFFERING); + break; + } + default: + break; + } +} + +// Called inside CONTROLLER and GAME threads +bool AvPlayerState::SetState(AvState state) { + std::lock_guard guard(m_state_machine_mutex); + + if (!IsStateTransitionValid(state)) { + return false; + } + m_previous_state.store(m_current_state); + m_current_state.store(state); + return true; +} + +// Called inside CONTROLLER thread +std::optional AvPlayerState::OnBufferingCheckEvent(u32 num_frames) { + if (!m_up_source) { + return std::nullopt; + } + return m_up_source->HasFrames(num_frames); +} + +// Called inside CONTROLLER thread +void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) { + LOG_INFO(Lib_AvPlayer, "Sending event to the game: id = {}", magic_enum::enum_name(event_id)); + const auto callback = m_event_replacement.event_callback; + if (callback) { + const auto ptr = m_event_replacement.object_ptr; + callback(ptr, event_id, 0, event_data); + } +} + +// Called inside CONTROLLER thread +int AvPlayerState::ProcessEvent() { + if (m_current_state.load() == AvState::Jump) { + return -2; + } + + std::lock_guard guard(m_event_handler_mutex); + + auto event = m_event_queue.Pop(); + if (!event.has_value()) { + return -1; + } + switch (event->event) { + case AvEventType::RevertState: { + SetState(m_previous_state.load()); + break; + } + case AvEventType::AddSource: { + if (m_up_source->FindStreamInfo()) { + SetState(AvState::Ready); + OnPlaybackStateChanged(AvState::Ready); + } else { + OnWarning(ORBIS_AVPLAYER_ERROR_NOT_SUPPORTED); + SetState(AvState::Error); + } + break; + } + case AvEventType::Error: { + OnWarning(event->payload.error); + SetState(AvState::Error); + break; + } + default: + break; + } + + return 0; +} + +// Called inside CONTROLLER thread +int AvPlayerState::UpdateBufferingState() { + if (m_current_state == AvState::Buffering) { + const auto has_frames = OnBufferingCheckEvent(10); + if (!has_frames.has_value()) { + return -1; + } + if (has_frames.value()) { + const auto state = + m_previous_state >= AvState::C0x0B ? m_previous_state.load() : AvState::Play; + SetState(state); + OnPlaybackStateChanged(state); + } + } else if (m_current_state == AvState::Play) { + const auto has_frames = OnBufferingCheckEvent(0); + if (!has_frames.has_value()) { + return -1; + } + if (!has_frames.value()) { + SetState(AvState::Buffering); + OnPlaybackStateChanged(AvState::Buffering); + } + } + return 0; +} + +bool AvPlayerState::IsStateTransitionValid(AvState state) { + switch (state) { + case AvState::Play: { + switch (m_current_state.load()) { + case AvState::Stop: + case AvState::EndOfFile: + // case AvState::C0x08: + case AvState::Error: + return false; + default: + return true; + } + } + case AvState::Pause: { + switch (m_current_state.load()) { + case AvState::Stop: + case AvState::EndOfFile: + // case AvState::C0x08: + case AvState::Starting: + case AvState::Error: + return false; + default: + return true; + } + } + case AvState::Jump: { + switch (m_current_state.load()) { + case AvState::Stop: + case AvState::EndOfFile: + // case AvState::C0x08: + case AvState::TrickMode: + case AvState::Starting: + case AvState::Error: + return false; + default: + return true; + } + } + case AvState::TrickMode: { + switch (m_current_state.load()) { + case AvState::Stop: + case AvState::EndOfFile: + // case AvState::C0x08: + case AvState::Jump: + case AvState::Starting: + case AvState::Error: + return false; + default: + return true; + } + } + case AvState::Buffering: { + switch (m_current_state.load()) { + case AvState::Stop: + case AvState::EndOfFile: + case AvState::Pause: + // case AvState::C0x08: + case AvState::Starting: + case AvState::Error: + return false; + default: + return true; + } + } + default: + return true; + } +} + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h new file mode 100644 index 00000000..fc822884 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" +#include "avplayer_data_streamer.h" +#include "avplayer_source.h" + +#include "core/libraries/kernel/thread_management.h" + +#include + +namespace Libraries::AvPlayer { + +class Stream; +class AvDecoder; + +class AvPlayerState : public AvPlayerStateCallback { +public: + AvPlayerState(const SceAvPlayerInitData& init_data, const ThreadPriorities& priorities); + ~AvPlayerState(); + + s32 AddSource(std::string_view filename, SceAvPlayerSourceType source_type); + s32 GetStreamCount(); + s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info); + s32 EnableStream(u32 stream_id); + s32 Start(); + bool Stop(); + bool GetAudioData(SceAvPlayerFrameInfo& audio_info); + bool GetVideoData(SceAvPlayerFrameInfo& video_info); + bool GetVideoData(SceAvPlayerFrameInfoEx& video_info); + bool IsActive(); + u64 CurrentTime(); + +private: + using ScePthreadMutex = Kernel::ScePthreadMutex; + using ScePthread = Kernel::ScePthread; + + // Event Replacement + static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, s32 event_id, s32 source_id, + void* event_data); + + void OnWarning(u32 id) override; + void OnError() override; + void OnEOF() override; + + void OnPlaybackStateChanged(AvState state); + std::optional OnBufferingCheckEvent(u32 num_frames); + + void EmitEvent(SceAvPlayerEvents event_id, void* event_data = nullptr); + bool SetState(AvState state); + + static void* PS4_SYSV_ABI AvControllerThread(void* p_user_data); + + void AddSourceEvent(); + int StartControllerThread(); + int ProcessEvent(); + int UpdateBufferingState(); + bool IsStateTransitionValid(AvState state); + + std::unique_ptr m_up_source; + + SceAvPlayerMemAllocator m_memory_replacement{}; + SceAvPlayerFileReplacement m_file_replacement{}; + SceAvPlayerEventReplacement m_event_replacement{}; + SceAvPlayerEventReplacement m_user_event_replacement{}; + ThreadPriorities m_thread_priorities{}; + bool m_auto_start{}; + u8 m_default_language[4]{}; + + std::atomic_int32_t m_quit; + std::atomic m_current_state; + std::atomic m_previous_state; + u32 m_thread_priority; + u32 m_thread_affinity; + std::atomic_uint32_t m_some_event_result{}; + + PthreadMutex m_state_machine_mutex{}; + PthreadMutex m_event_handler_mutex{}; + ScePthread m_controller_thread{}; + AvPlayerQueue m_event_queue{}; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index 5eabaaf6..74aeef67 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -457,5 +457,18 @@ constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624; constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = 0x80551613; constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551622; +// AvPlayer library +constexpr int ORBIS_AVPLAYER_ERROR_INVALID_PARAMS = 0x806A0001; +constexpr int ORBIS_AVPLAYER_ERROR_OPERATION_FAILED = 0x806A0002; +constexpr int ORBIS_AVPLAYER_ERROR_NO_MEMORY = 0x806A0003; +constexpr int ORBIS_AVPLAYER_ERROR_NOT_SUPPORTED = 0x806A0004; +constexpr int ORBIS_AVPLAYER_ERROR_WAR_FILE_NONINTERLEAVED = 0x806A00A0; +constexpr int ORBIS_AVPLAYER_ERROR_WAR_LOOPING_BACK = 0x806A00A1; +constexpr int ORBIS_AVPLAYER_ERROR_WAR_JUMP_COMPLETE = 0x806A00A3; +constexpr int ORBIS_AVPLAYER_ERROR_INFO_MARLIN_ENCRY = 0x806A00B0; +constexpr int ORBIS_AVPLAYER_ERROR_INFO_PLAYREADY_ENCRY = 0x806A00B4; +constexpr int ORBIS_AVPLAYER_ERROR_INFO_AES_ENCRY = 0x806A00B5; +constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF; + // AppContent library constexpr int ORBIS_APP_CONTENT_ERROR_PARAMETER = 0x80D90002; diff --git a/src/core/libraries/kernel/thread_management.h b/src/core/libraries/kernel/thread_management.h index c5935275..bd555ccc 100644 --- a/src/core/libraries/kernel/thread_management.h +++ b/src/core/libraries/kernel/thread_management.h @@ -158,19 +158,24 @@ void init_pthreads(); void pthreadInitSelfMainThread(); int PS4_SYSV_ABI scePthreadAttrInit(ScePthreadAttr* attr); +int PS4_SYSV_ABI scePthreadAttrDestroy(ScePthreadAttr* attr); int PS4_SYSV_ABI scePthreadAttrSetdetachstate(ScePthreadAttr* attr, int detachstate); int PS4_SYSV_ABI scePthreadAttrSetinheritsched(ScePthreadAttr* attr, int inheritSched); int PS4_SYSV_ABI scePthreadAttrSetschedparam(ScePthreadAttr* attr, const SceKernelSchedParam* param); int PS4_SYSV_ABI scePthreadAttrSetschedpolicy(ScePthreadAttr* attr, int policy); -ScePthread PS4_SYSV_ABI scePthreadSelf(); int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr, const /*SceKernelCpumask*/ u64 mask); +int PS4_SYSV_ABI scePthreadAttrSetstacksize(ScePthreadAttr* attr, size_t stack_size); + +ScePthread PS4_SYSV_ABI scePthreadSelf(); int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpumask*/ u64 mask); int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u64* mask); int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr, PthreadEntryFunc start_routine, void* arg, const char* name); - +[[noreturn]] void PS4_SYSV_ABI scePthreadExit(void* value_ptr); +int PS4_SYSV_ABI scePthreadJoin(ScePthread thread, void** res); +int PS4_SYSV_ABI scePthreadGetprio(ScePthread thread, int* prio); int PS4_SYSV_ABI scePthreadSetprio(ScePthread thread, int prio); /*** @@ -178,11 +183,14 @@ int PS4_SYSV_ABI scePthreadSetprio(ScePthread thread, int prio); */ int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr, const char* name); +int PS4_SYSV_ABI scePthreadMutexDestroy(ScePthreadMutex* mutex); +int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex); +int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex); + +int PS4_SYSV_ABI scePthreadMutexattrDestroy(ScePthreadMutexattr* attr); int PS4_SYSV_ABI scePthreadMutexattrInit(ScePthreadMutexattr* attr); int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type); int PS4_SYSV_ABI scePthreadMutexattrSetprotocol(ScePthreadMutexattr* attr, int protocol); -int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex); -int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex); /**** * Cond calls */ diff --git a/src/core/libraries/kernel/time_management.h b/src/core/libraries/kernel/time_management.h index a28f8c13..8934171d 100644 --- a/src/core/libraries/kernel/time_management.h +++ b/src/core/libraries/kernel/time_management.h @@ -50,6 +50,7 @@ u64 PS4_SYSV_ABI sceKernelGetProcessTime(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency(); u64 PS4_SYSV_ABI sceKernelReadTsc(); +int PS4_SYSV_ABI sceKernelUsleep(u32 microseconds); void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym);