From b5c69189e56d8813b10adb2d2bcf17426b3bdac0 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Fri, 26 Jul 2024 18:34:36 +0300 Subject: [PATCH 1/6] 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); From e33ff10212113a4756d88f125a325e1a55168f68 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Mon, 12 Aug 2024 19:01:02 +0300 Subject: [PATCH 2/6] Added some logs, fixed some crashes, fixed align. --- src/audio_core/sdl_audio.cpp | 2 +- src/core/libraries/avplayer/avplayer.cpp | 16 +- src/core/libraries/avplayer/avplayer.h | 2 + .../avplayer/avplayer_file_streamer.cpp | 33 +- .../avplayer/avplayer_file_streamer.h | 5 +- src/core/libraries/avplayer/avplayer_impl.cpp | 18 +- src/core/libraries/avplayer/avplayer_impl.h | 6 +- .../libraries/avplayer/avplayer_source.cpp | 406 ++++++++++-------- src/core/libraries/avplayer/avplayer_source.h | 67 +-- .../libraries/avplayer/avplayer_state.cpp | 160 +++---- src/core/libraries/avplayer/avplayer_state.h | 10 +- 11 files changed, 398 insertions(+), 327 deletions(-) diff --git a/src/audio_core/sdl_audio.cpp b/src/audio_core/sdl_audio.cpp index 141d338e..aac67d8c 100644 --- a/src/audio_core/sdl_audio.cpp +++ b/src/audio_core/sdl_audio.cpp @@ -100,7 +100,7 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) { int result = SDL_PutAudioStreamData(port.stream, ptr, port.samples_num * port.sample_size * port.channels_num); // TODO find a correct value 8192 is estimated - while (SDL_GetAudioStreamAvailable(port.stream) > 8192) { + while (SDL_GetAudioStreamAvailable(port.stream) > 65536) { SDL_Delay(0); } diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index 41f8d076..e8e99bca 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -9,7 +9,9 @@ #include "core/libraries/kernel/thread_management.h" #include "core/libraries/libs.h" -#include +#include // std::max, std::min + +#include // va_list namespace Libraries::AvPlayer { @@ -148,9 +150,7 @@ SceAvPlayerHandle PS4_SYSV_ABI sceAvPlayerInit(SceAvPlayerInitData* data) { // 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; + return new AvPlayer(*data, priorities); } s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data, @@ -225,9 +225,7 @@ s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data, // } // priorities.http_streaming_affinity = p_data->http_streaming_affinity; - const auto player = new AvPlayer(); - player->Init(data, priorities); - *p_player = player; + *p_player = new AvPlayer(data, priorities); return ORBIS_OK; } @@ -290,13 +288,13 @@ s32 PS4_SYSV_ABI sceAvPlayerSetAvSyncMode(SceAvPlayerHandle handle, return ORBIS_OK; } -s32 PS4_SYSV_ABI sceAvPlayerSetLogCallback(SceAvPlayerLogCallback logCb, void* user_data) { +s32 PS4_SYSV_ABI sceAvPlayerSetLogCallback(SceAvPlayerLogCallback log_cb, void* user_data) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceAvPlayerSetLooping(SceAvPlayerHandle handle, bool loop_flag) { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called, looping = {}", loop_flag); if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } diff --git a/src/core/libraries/avplayer/avplayer.h b/src/core/libraries/avplayer/avplayer.h index f5589441..e88419c1 100644 --- a/src/core/libraries/avplayer/avplayer.h +++ b/src/core/libraries/avplayer/avplayer.h @@ -5,6 +5,8 @@ #include "common/types.h" +#include // size_t + namespace Core::Loader { class SymbolsResolver; } diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.cpp b/src/core/libraries/avplayer/avplayer_file_streamer.cpp index 1c54c454..43055fd5 100644 --- a/src/core/libraries/avplayer/avplayer_file_streamer.cpp +++ b/src/core/libraries/avplayer/avplayer_file_streamer.cpp @@ -12,23 +12,30 @@ extern "C" { #include } +#include // std::max, std::min + #define AVPLAYER_AVIO_BUFFER_SIZE 4096 namespace Libraries::AvPlayer { -AvPlayerFileStreamer::AvPlayerFileStreamer(SceAvPlayerFileReplacement& file_replacement, +AvPlayerFileStreamer::AvPlayerFileStreamer(const SceAvPlayerFileReplacement& file_replacement, std::string_view path) : m_file_replacement(file_replacement) { - Init(path); + const auto ptr = m_file_replacement.object_ptr; + m_fd = m_file_replacement.open(ptr, path.data()); + ASSERT(m_fd >= 0); + m_file_size = m_file_replacement.size(ptr); + // avio_buffer is deallocated in `avio_context_free` + const auto avio_buffer = reinterpret_cast(av_malloc(AVPLAYER_AVIO_BUFFER_SIZE)); + m_avio_context = + avio_alloc_context(avio_buffer, AVPLAYER_AVIO_BUFFER_SIZE, 0, this, + &AvPlayerFileStreamer::ReadPacket, nullptr, &AvPlayerFileStreamer::Seek); } 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; @@ -36,20 +43,6 @@ AvPlayerFileStreamer::~AvPlayerFileStreamer() { } } -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) { @@ -61,7 +54,7 @@ s32 AvPlayerFileStreamer::ReadPacket(void* opaque, u8* buffer, s32 size) { 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) { + if (bytes_read == 0 && size != 0) { return AVERROR_EOF; } self->m_position += bytes_read; diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.h b/src/core/libraries/avplayer/avplayer_file_streamer.h index 9f1442f9..658ce8c1 100644 --- a/src/core/libraries/avplayer/avplayer_file_streamer.h +++ b/src/core/libraries/avplayer/avplayer_file_streamer.h @@ -15,7 +15,7 @@ namespace Libraries::AvPlayer { class AvPlayerFileStreamer : public IDataStreamer { public: - AvPlayerFileStreamer(SceAvPlayerFileReplacement& file_replacement, std::string_view path); + AvPlayerFileStreamer(const SceAvPlayerFileReplacement& file_replacement, std::string_view path); ~AvPlayerFileStreamer(); AVIOContext* GetContext() override { @@ -23,8 +23,6 @@ public: } 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); @@ -33,7 +31,6 @@ private: int m_fd = -1; u64 m_position{}; u64 m_file_size{}; - u8* m_avio_buffer{}; AVIOContext* m_avio_context{}; }; diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp index 58793da7..f08356bd 100644 --- a/src/core/libraries/avplayer/avplayer_impl.cpp +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -77,11 +77,9 @@ u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { 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; +AvPlayer::AvPlayer(const SceAvPlayerInitData& data, const ThreadPriorities& priorities) + : m_file_io_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_FileIO"), 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; @@ -120,6 +118,9 @@ s32 AvPlayer::AddSource(std::string_view path) { } s32 AvPlayer::GetStreamCount() { + if (m_state == nullptr) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } return m_state->GetStreamCount(); } @@ -130,11 +131,14 @@ s32 AvPlayer::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { return ORBIS_OK; } -s32 AvPlayer::EnableStream(u32 stream_id) { +s32 AvPlayer::EnableStream(u32 stream_index) { if (m_state == nullptr) { return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; } - return m_state->EnableStream(stream_id); + if (!m_state->EnableStream(stream_index)) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + return ORBIS_OK; } s32 AvPlayer::Start() { diff --git a/src/core/libraries/avplayer/avplayer_impl.h b/src/core/libraries/avplayer/avplayer_impl.h index fe3abcd1..98f959e6 100644 --- a/src/core/libraries/avplayer/avplayer_impl.h +++ b/src/core/libraries/avplayer/avplayer_impl.h @@ -33,15 +33,13 @@ public: 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); + AvPlayer(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 EnableStream(u32 stream_index); s32 Start(); bool GetAudioData(SceAvPlayerFrameInfo& audio_info); bool GetVideoData(SceAvPlayerFrameInfo& video_info); diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index c1dc6d57..c3d3dc55 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -23,67 +23,47 @@ 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; - } +AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state, std::string_view path, + const SceAvPlayerInitData& init_data, ThreadPriorities& priorities, + SceAvPlayerSourceType source_type) + : m_state(state), m_priorities(priorities), m_memory_replacement(init_data.memory_replacement), + m_num_output_video_framebuffers(init_data.num_output_video_framebuffers) { + AVFormatContext* context = avformat_alloc_context(); + if (init_data.file_replacement.open != nullptr) { + m_up_data_streamer = + std::make_unique(init_data.file_replacement, path); + context->pb = m_up_data_streamer->GetContext(); + ASSERT(!AVPLAYER_IS_ERROR(avformat_open_input(&context, nullptr, nullptr, nullptr))); } 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; - } + ASSERT(!AVPLAYER_IS_ERROR( + avformat_open_input(&context, filepath.string().c_str(), nullptr, nullptr))); } + m_avformat_context = AVFormatContextPtr(context, &ReleaseAVFormatContext); +} - return 0; +AvPlayerSource::~AvPlayerSource() { + Stop(); } bool AvPlayerSource::FindStreamInfo() { if (m_avformat_context == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not find stream info. NULL context."); return false; } if (m_avformat_context->nb_streams > 0) { return true; } - return avformat_find_stream_info(m_avformat_context, nullptr) == 0; + return avformat_find_stream_info(m_avformat_context.get(), nullptr) == 0; } s32 AvPlayerSource::GetStreamCount() { if (m_avformat_context == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not get stream count. NULL context."); return -1; } - LOG_DEBUG(Lib_AvPlayer, "num streams: {}", m_avformat_context->nb_streams); + LOG_INFO(Lib_AvPlayer, "Stream Count: {}", m_avformat_context->nb_streams); return m_avformat_context->nb_streams; } @@ -108,70 +88,68 @@ static f32 AVRationalToF32(AVRational rational) { s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { info = {}; if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) { + LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info.", stream_index); return -1; } const auto p_stream = m_avformat_context->streams[stream_index]; if (p_stream == nullptr || p_stream->codecpar == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info. NULL stream.", stream_index); 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; + if (p_lang_node != nullptr) { + LOG_INFO(Lib_AvPlayer, "Stream {} language = {}", stream_index, p_lang_node->value); + } else { + LOG_WARNING(Lib_AvPlayer, "Stream {} language is unknown", stream_index); } - 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); + LOG_INFO(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)); + if (p_lang_node != nullptr) { + 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); + LOG_INFO(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)); + if (p_lang_node != nullptr) { + 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)); + if (p_lang_node != nullptr) { + std::memcpy(info.details.subs.language_code, p_lang_node->value, + std::min(strlen(p_lang_node->value), 3ull)); + } break; default: + LOG_ERROR(Lib_AvPlayer, "Stream {} type is unknown: {}.", stream_index, info.type); 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) { +bool AvPlayerSource::EnableStream(u32 stream_index) { if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) { - return -1; + return false; } const auto stream = m_avformat_context->streams[stream_index]; const auto decoder = avcodec_find_decoder(stream->codecpar->codec_id); if (decoder == nullptr) { - return -1; + return false; } switch (stream->codecpar->codec_type) { case AVMediaType::AVMEDIA_TYPE_VIDEO: { @@ -179,10 +157,18 @@ s32 AvPlayerSource::EnableStream(u32 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; + LOG_ERROR(Lib_AvPlayer, "Could not copy stream {} avcodec parameters to context.", + stream_index); + return false; } if (avcodec_open2(m_video_codec_context.get(), decoder, nullptr) < 0) { - return -1; + LOG_ERROR(Lib_AvPlayer, "Could not open avcodec for video stream {}.", stream_index); + return false; + } + const auto width = m_video_codec_context->width; + const auto size = (width * m_video_codec_context->height * 3) / 2; + for (u64 index = 0; index < m_num_output_video_framebuffers; ++index) { + m_video_buffers.Push(FrameBuffer(m_memory_replacement, 0x100, size)); } LOG_INFO(Lib_AvPlayer, "Video stream {} enabled", stream_index); break; @@ -192,10 +178,19 @@ s32 AvPlayerSource::EnableStream(u32 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; + LOG_ERROR(Lib_AvPlayer, "Could not copy stream {} avcodec parameters to context.", + stream_index); + return false; } if (avcodec_open2(m_audio_codec_context.get(), decoder, nullptr) < 0) { - return -1; + LOG_ERROR(Lib_AvPlayer, "Could not open avcodec for audio stream {}.", stream_index); + return false; + } + const auto num_channels = m_audio_codec_context->ch_layout.nb_channels; + const auto align = num_channels * sizeof(u16); + const auto size = num_channels * sizeof(u16) * 1024; + for (u64 index = 0; index < 2; ++index) { + m_audio_buffers.Push(FrameBuffer(m_memory_replacement, 0x100, size)); } LOG_INFO(Lib_AvPlayer, "Audio stream {} enabled", stream_index); break; @@ -205,35 +200,7 @@ s32 AvPlayerSource::EnableStream(u32 stream_index) { 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(); + return true; } void AvPlayerSource::SetLooping(bool is_looping) { @@ -241,11 +208,12 @@ void AvPlayerSource::SetLooping(bool is_looping) { } std::optional AvPlayerSource::HasFrames(u32 num_frames) { - return m_video_frames.Size() > num_frames; + return m_video_frames.Size() > num_frames || m_is_eof; } s32 AvPlayerSource::Start() { if (m_audio_codec_context == nullptr && m_video_codec_context == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not start playback. NULL context."); return -1; } { @@ -258,6 +226,7 @@ s32 AvPlayerSource::Start() { }; m_demuxer_thread = CreateThread(&DemuxerThread, demuxer_params); if (m_demuxer_thread == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not create DEMUXER thread."); return -1; } } @@ -271,6 +240,7 @@ s32 AvPlayerSource::Start() { }; m_video_decoder_thread = CreateThread(&VideoDecoderThread, video_decoder_params); if (m_video_decoder_thread == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not create VIDEO DECODER thread."); return -1; } } @@ -284,6 +254,7 @@ s32 AvPlayerSource::Start() { }; m_audio_decoder_thread = CreateThread(&AudioDecoderThread, audio_decoder_params); if (m_audio_decoder_thread == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not create AUDIO DECODER thread."); return -1; } } @@ -292,6 +263,11 @@ s32 AvPlayerSource::Start() { } bool AvPlayerSource::Stop() { + if (m_is_stop) { + LOG_WARNING(Lib_AvPlayer, "Could not stop playback: already stopped."); + return false; + } + m_is_stop = true; void* res = nullptr; @@ -304,6 +280,18 @@ bool AvPlayerSource::Stop() { if (m_demuxer_thread != nullptr) { scePthreadJoin(m_demuxer_thread, &res); } + m_audio_packets.Clear(); + m_video_packets.Clear(); + m_audio_frames.Clear(); + m_video_frames.Clear(); + if (m_current_audio_frame.has_value()) { + m_audio_buffers.Push(std::move(m_current_audio_frame.value())); + m_current_audio_frame.reset(); + } + if (m_current_video_frame.has_value()) { + m_video_buffers.Push(std::move(m_current_video_frame.value())); + m_current_video_frame.reset(); + } return true; } @@ -333,44 +321,27 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { auto frame = m_video_frames.Pop(); if (!frame.has_value()) { + LOG_WARNING(Lib_AvPlayer, "Could get video frame: no frames."); 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); + while (elapsed_time < frame->info.timestamp) { + sceKernelUsleep((frame->info.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 the buffer to the queue + if (m_current_video_frame.has_value()) { + m_video_buffers.Push(std::move(m_current_video_frame.value())); + } + m_current_video_frame = std::move(frame->buffer); + video_info = frame->info; return true; } @@ -381,45 +352,38 @@ bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { auto frame = m_audio_frames.Pop(); if (!frame.has_value()) { + LOG_WARNING(Lib_AvPlayer, "Could get audio frame: no frames."); 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); + while (elapsed_time < frame->info.timestamp) { + sceKernelUsleep((frame->info.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); + // return the buffer to the queue + if (m_current_audio_frame.has_value()) { + m_audio_buffers.Push(std::move(m_current_audio_frame.value())); + } + m_current_audio_frame = std::move(frame->buffer); 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; + audio_info.timestamp = frame->info.timestamp; + audio_info.pData = reinterpret_cast(frame->info.pData); + audio_info.details.audio.size = frame->info.details.audio.size; + audio_info.details.audio.channel_count = frame->info.details.audio.channel_count; 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; + using namespace std::chrono; + return duration_cast(high_resolution_clock::now() - m_start_time).count(); } bool AvPlayerSource::IsActive() { @@ -457,12 +421,19 @@ void AvPlayerSource::ReleaseSWSContext(SwsContext* context) { } } +void AvPlayerSource::ReleaseAVFormatContext(AVFormatContext* context) { + if (context != nullptr) { + avformat_close_input(&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()) { + LOG_WARNING(Lib_AvPlayer, "Could not start DEMUXER thread. No streams enabled."); return nullptr; } + LOG_INFO(Lib_AvPlayer, "Demuxer Thread started"); while (!self->m_is_stop) { if (self->m_video_packets.Size() > 60) { @@ -470,10 +441,10 @@ void* AvPlayerSource::DemuxerThread(void* opaque) { continue; } AVPacketPtr up_packet(av_packet_alloc(), &ReleaseAVPacket); - const auto res = av_read_frame(self->m_avformat_context, up_packet.get()); + const auto res = av_read_frame(self->m_avformat_context.get(), up_packet.get()); if (res < 0) { if (res == AVERROR_EOF) { - LOG_TRACE(Lib_AvPlayer, "EOF reached in demuxer"); + LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer"); break; } else { LOG_ERROR(Lib_AvPlayer, "Could not read AV frame: error = {}", res); @@ -500,7 +471,7 @@ void* AvPlayerSource::DemuxerThread(void* opaque) { } self->m_state.OnEOF(); - LOG_TRACE(Lib_AvPlayer, "Demuxer Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normaly"); scePthreadExit(0); } @@ -531,12 +502,47 @@ AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& fram return nv12_frame; } +Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame) { + ASSERT(frame.format == AV_PIX_FMT_NV12); + + auto p_buffer = buffer.GetBuffer(); + CopyNV12Data(p_buffer, frame); + + const auto pkt_dts = u64(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; + + return Frame{ + .buffer = std::move(buffer), + .info = + { + .pData = p_buffer, + .timestamp = timestamp, + .details = + { + .video = + { + .width = u32(frame.width), + .height = u32(frame.height), + .aspect_ratio = AVRationalToF32(frame.sample_aspect_ratio), + .pitch = u32(frame.linesize[0]), + .luma_bit_depth = 8, + .chroma_bit_depth = 8, + }, + }, + }, + }; +} + void* AvPlayerSource::VideoDecoderThread(void* opaque) { - LOG_TRACE(Lib_AvPlayer, "Video Decoder Thread started"); + LOG_INFO(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) { + if (self->m_video_packets.Size() == 0) { sceKernelUsleep(5000); continue; } @@ -544,6 +550,7 @@ void* AvPlayerSource::VideoDecoderThread(void* opaque) { 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(); @@ -552,11 +559,15 @@ void* AvPlayerSource::VideoDecoderThread(void* opaque) { scePthreadExit(nullptr); } while (res >= 0) { + if (self->m_video_buffers.Size() == 0 && !self->m_is_stop) { + sceKernelUsleep(5000); + continue; + } 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"); + LOG_INFO(Lib_AvPlayer, "EOF reached in video decoder"); scePthreadExit(nullptr); } else if (res != AVERROR(EAGAIN)) { LOG_ERROR(Lib_AvPlayer, @@ -566,18 +577,26 @@ void* AvPlayerSource::VideoDecoderThread(void* opaque) { 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)); + auto buffer = self->m_video_buffers.Pop(); + if (!buffer.has_value()) { + // Video buffers queue was cleared. This means that player was stopped. + break; } + if (up_frame->format != AV_PIX_FMT_NV12) { + const auto nv12_frame = self->ConvertVideoFrame(*up_frame); + self->m_video_frames.Push( + self->PrepareVideoFrame(std::move(buffer.value()), *nv12_frame)); + } else { + self->m_video_frames.Push( + self->PrepareVideoFrame(std::move(buffer.value()), *up_frame)); + } + LOG_TRACE(Lib_AvPlayer, "Produced Video Frame. Num Frames: {}", + self->m_video_frames.Size()); } } } - LOG_TRACE(Lib_AvPlayer, "Video Decoder Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normaly"); scePthreadExit(nullptr); } @@ -607,12 +626,45 @@ AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& fram return pcm16_frame; } +Frame AvPlayerSource::PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame) { + ASSERT(frame.format == AV_SAMPLE_FMT_S16); + ASSERT(frame.nb_samples <= 1024); + + auto p_buffer = buffer.GetBuffer(); + const auto size = frame.ch_layout.nb_channels * frame.nb_samples * sizeof(u16); + std::memcpy(p_buffer, frame.data[0], size); + + const auto pkt_dts = u64(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; + + return Frame{ + .buffer = std::move(buffer), + .info = + { + .pData = p_buffer, + .timestamp = timestamp, + .details = + { + .audio = + { + .channel_count = u16(frame.ch_layout.nb_channels), + .size = u32(size), + }, + }, + }, + }; +} + void* AvPlayerSource::AudioDecoderThread(void* opaque) { - LOG_TRACE(Lib_AvPlayer, "Audio Decoder Thread started"); + LOG_INFO(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) { + if (self->m_audio_packets.Size() == 0) { sceKernelUsleep(5000); continue; } @@ -628,11 +680,15 @@ void* AvPlayerSource::AudioDecoderThread(void* opaque) { scePthreadExit(nullptr); } while (res >= 0) { + if (self->m_audio_buffers.Size() == 0 && !self->m_is_stop) { + sceKernelUsleep(5000); + continue; + } 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"); + LOG_INFO(Lib_AvPlayer, "EOF reached in audio decoder"); scePthreadExit(nullptr); } else if (res != AVERROR(EAGAIN)) { self->m_state.OnError(); @@ -642,16 +698,26 @@ void* AvPlayerSource::AudioDecoderThread(void* opaque) { 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)); + auto buffer = self->m_audio_buffers.Pop(); + if (!buffer.has_value()) { + // Audio buffers queue was cleared. This means that player was stopped. + break; } + if (up_frame->format != AV_SAMPLE_FMT_S16) { + const auto pcm16_frame = self->ConvertAudioFrame(*up_frame); + self->m_audio_frames.Push( + self->PrepareAudioFrame(std::move(buffer.value()), *pcm16_frame)); + } else { + self->m_audio_frames.Push( + self->PrepareAudioFrame(std::move(buffer.value()), *up_frame)); + } + LOG_TRACE(Lib_AvPlayer, "Produced Audio Frame. Num Frames: {}", + self->m_audio_frames.Size()); } } } - LOG_TRACE(Lib_AvPlayer, "Audio Decoder Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normaly"); scePthreadExit(nullptr); } diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h index 81f77a62..67ad44d8 100644 --- a/src/core/libraries/avplayer/avplayer_source.h +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -38,11 +38,13 @@ 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) {} + m_data(Allocate(memory_replacement, align, size)) { + ASSERT_MSG(m_data, "Could not allocated frame buffer."); + } ~FrameBuffer() { - if (!m_data.empty()) { - Deallocate(m_memory_replacement, m_data.data()); + if (m_data != nullptr) { + Deallocate(m_memory_replacement, m_data); m_data = {}; } } @@ -52,17 +54,16 @@ public: FrameBuffer(FrameBuffer&& r) noexcept : m_memory_replacement(r.m_memory_replacement), m_data(r.m_data) { - r.m_data = {}; + r.m_data = nullptr; }; 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(); + return m_data; } private: @@ -75,23 +76,26 @@ private: memory_replacement.deallocate(memory_replacement.object_ptr, ptr); } - SceAvPlayerMemAllocator m_memory_replacement; - std::span m_data; + const SceAvPlayerMemAllocator& m_memory_replacement; + u8* m_data = nullptr; +}; + +struct Frame { + FrameBuffer buffer; + SceAvPlayerFrameInfoEx info; }; class AvPlayerSource { public: - AvPlayerSource(AvPlayerStateCallback& state); + AvPlayerSource(AvPlayerStateCallback& state, std::string_view path, + const SceAvPlayerInitData& init_data, ThreadPriorities& priorities, + SceAvPlayerSourceType source_type); ~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); + bool EnableStream(u32 stream_index); void SetLooping(bool is_looping); std::optional HasFrames(u32 num_frames); s32 Start(); @@ -114,52 +118,55 @@ private: static void ReleaseAVCodecContext(AVCodecContext* context); static void ReleaseSWRContext(SwrContext* context); static void ReleaseSWSContext(SwsContext* context); + static void ReleaseAVFormatContext(AVFormatContext* 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); + using AVFormatContextPtr = std::unique_ptr; AVFramePtr ConvertAudioFrame(const AVFrame& frame); AVFramePtr ConvertVideoFrame(const AVFrame& frame); - u64 m_last_video_timestamp{}; + Frame PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame); + Frame PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame); AvPlayerStateCallback& m_state; - ThreadPriorities m_priorities; - SceAvPlayerMemAllocator m_memory_replacement; + ThreadPriorities m_priorities{}; + SceAvPlayerMemAllocator m_memory_replacement{}; + u64 m_num_output_video_framebuffers{}; 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_buffers; + AvPlayerQueue m_video_buffers; AvPlayerQueue m_audio_packets; AvPlayerQueue m_video_packets; - AvPlayerQueue m_audio_frames; - AvPlayerQueue m_video_frames; + AvPlayerQueue m_audio_frames; + AvPlayerQueue m_video_frames; - std::span m_video_frame_storage; - std::span m_audio_frame_storage; + std::optional m_current_video_frame; + std::optional m_current_audio_frame; - 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{}; + 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{}; + AVFormatContextPtr m_avformat_context{nullptr, &ReleaseAVFormatContext}; + AVCodecContextPtr m_video_codec_context{nullptr, &ReleaseAVCodecContext}; + AVCodecContextPtr m_audio_codec_context{nullptr, &ReleaseAVCodecContext}; SWRContextPtr m_swr_context{nullptr, &ReleaseSWRContext}; SWSContextPtr m_sws_context{nullptr, &ReleaseSWSContext}; diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index 3048819e..e7aa4571 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -39,38 +39,29 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_i 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; - } + video_stream_index = stream_index; + } + if (!default_language.empty() && + 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; - } + audio_stream_index = stream_index; + } + if (!default_language.empty() && + 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; - } + 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; } @@ -89,8 +80,9 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_i return; } - const auto callback = self->m_user_event_replacement.event_callback; - const auto ptr = self->m_user_event_replacement.object_ptr; + // Pass other events to the game + const auto callback = self->m_event_replacement.event_callback; + const auto ptr = self->m_event_replacement.object_ptr; if (callback != nullptr) { callback(ptr, event_id, 0, event_data); } @@ -99,34 +91,15 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_i // 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_init_data(init_data), m_event_replacement(init_data.event_replacement), + m_thread_priorities(priorities), + m_event_handler_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_EventHandler"), + m_state_machine_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_StateMachine") { + if (m_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_init_data.event_replacement.event_callback = &AvPlayerState::AutoPlayEventCallback; + m_init_data.event_replacement.object_ptr = this; } - 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)); } @@ -135,54 +108,67 @@ AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data, } AvPlayerState::~AvPlayerState() { - m_event_queue.Clear(); if (m_up_source && m_current_state == AvState::Play) { m_up_source->Stop(); - OnPlaybackStateChanged(AvState::Stop); } + m_quit = true; + if (m_controller_thread != nullptr) { + void* res = nullptr; + scePthreadJoin(m_controller_thread, &res); + } + m_event_queue.Clear(); } // Called inside GAME thread s32 AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType source_type) { if (path.empty()) { + LOG_ERROR(Lib_AvPlayer, "File path is empty."); return -1; } - if (m_up_source) { + if (m_up_source != nullptr) { + LOG_ERROR(Lib_AvPlayer, "Only one source is supported."); 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; - } + m_up_source = std::make_unique(*this, path, m_init_data, m_thread_priorities, + source_type); AddSourceEvent(); - return 0; } // Called inside GAME thread s32 AvPlayerState::GetStreamCount() { + if (m_up_source == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not get stream count. No source."); + return -1; + } return m_up_source->GetStreamCount(); } // Called inside GAME thread s32 AvPlayerState::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { + if (m_up_source == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info. No source.", stream_index); + return -1; + } return m_up_source->GetStreamInfo(stream_index, info); } // Called inside GAME thread s32 AvPlayerState::Start() { - if (m_up_source == nullptr) { + if (m_up_source == nullptr || m_up_source->Start() < 0) { + LOG_ERROR(Lib_AvPlayer, "Could not start playback."); return -1; } - return m_up_source->Start(); + SetState(AvState::Play); + OnPlaybackStateChanged(AvState::Play); + return 0; } void* AvPlayerState::AvControllerThread(void* p_user_data) { AvPlayerState* self = reinterpret_cast(p_user_data); - while (self->m_quit.load() == 0) { + while (!self->m_quit) { if (self->m_event_queue.Size() != 0) { self->ProcessEvent(); continue; @@ -201,6 +187,16 @@ void AvPlayerState::AddSourceEvent() { }); } +void AvPlayerState::WarningEvent(s32 id) { + m_event_queue.Push(AvPlayerEvent{ + .event = AvEventType::WarningId, + .payload = + { + .error = id, + }, + }); +} + // Called inside GAME thread int AvPlayerState::StartControllerThread() { m_quit.store(0); @@ -214,25 +210,28 @@ int AvPlayerState::StartControllerThread() { }; m_controller_thread = CreateThread(&AvControllerThread, params); if (m_controller_thread == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not create CONTROLLER thread."); return -1; } return 0; } // Called inside GAME thread -s32 AvPlayerState::EnableStream(u32 stream_id) { +bool AvPlayerState::EnableStream(u32 stream_index) { if (m_up_source == nullptr) { - return -1; + return false; } - return m_up_source->EnableStream(stream_id); + return m_up_source->EnableStream(stream_index); } // Called inside GAME thread bool AvPlayerState::Stop() { - if (m_up_source == nullptr) { + if (m_up_source == nullptr || m_current_state == AvState::Stop) { + return false; + } + if (!SetState(AvState::Stop)) { return false; } - SetState(AvState::Stop); OnPlaybackStateChanged(AvState::Stop); return m_up_source->Stop(); } @@ -268,13 +267,16 @@ bool AvPlayerState::IsActive() { u64 AvPlayerState::CurrentTime() { if (m_up_source == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not get current time. No source."); return 0; } return m_up_source->CurrentTime(); } +// May be called from different threads void AvPlayerState::OnWarning(u32 id) { - EmitEvent(SCE_AVPLAYER_WARNING_ID, &id); + // Forward to CONTROLLER thread + WarningEvent(id); } void AvPlayerState::OnError() { @@ -282,9 +284,7 @@ void AvPlayerState::OnError() { OnPlaybackStateChanged(AvState::Error); } -void AvPlayerState::OnEOF() { - Stop(); -} +void AvPlayerState::OnEOF() {} // Called inside CONTROLLER thread void AvPlayerState::OnPlaybackStateChanged(AvState state) { @@ -319,6 +319,8 @@ bool AvPlayerState::SetState(AvState state) { std::lock_guard guard(m_state_machine_mutex); if (!IsStateTransitionValid(state)) { + LOG_ERROR(Lib_AvPlayer, "Invalid state transition: {} -> {}", + magic_enum::enum_name(m_current_state.load()), magic_enum::enum_name(state)); return false; } m_previous_state.store(m_current_state); @@ -337,16 +339,16 @@ std::optional AvPlayerState::OnBufferingCheckEvent(u32 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; + const auto callback = m_init_data.event_replacement.event_callback; if (callback) { - const auto ptr = m_event_replacement.object_ptr; + const auto ptr = m_init_data.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) { + if (m_current_state == AvState::Jump) { return -2; } @@ -357,6 +359,10 @@ int AvPlayerState::ProcessEvent() { return -1; } switch (event->event) { + case AvEventType::WarningId: { + OnWarning(event->payload.error); + break; + } case AvEventType::RevertState: { SetState(m_previous_state.load()); break; diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h index fc822884..cfddab09 100644 --- a/src/core/libraries/avplayer/avplayer_state.h +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -24,7 +24,7 @@ public: s32 AddSource(std::string_view filename, SceAvPlayerSourceType source_type); s32 GetStreamCount(); s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info); - s32 EnableStream(u32 stream_id); + bool EnableStream(u32 stream_index); s32 Start(); bool Stop(); bool GetAudioData(SceAvPlayerFrameInfo& audio_info); @@ -54,6 +54,8 @@ private: static void* PS4_SYSV_ABI AvControllerThread(void* p_user_data); void AddSourceEvent(); + void WarningEvent(s32 id); + int StartControllerThread(); int ProcessEvent(); int UpdateBufferingState(); @@ -61,15 +63,13 @@ private: std::unique_ptr m_up_source; - SceAvPlayerMemAllocator m_memory_replacement{}; - SceAvPlayerFileReplacement m_file_replacement{}; + SceAvPlayerInitData m_init_data{}; 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_bool m_quit; std::atomic m_current_state; std::atomic m_previous_state; u32 m_thread_priority; From 0d6e8e227a7c71223ad5d154538a1e1213a661d3 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Wed, 14 Aug 2024 21:30:44 +0300 Subject: [PATCH 3/6] Fixed some sound and threading issues. Details: * Switched SDL audio mutex to RW lock. This fixes games that continiously call SetVolume in a different thread (like Ghostbusters) * Added contition to buffer audio packets independent of video packets. This fixes choppy audio across many games. * Increased the number of audio frame buffers from 2 to 4. Just in case. * Migrated to std::jthread and std::mutex from pthreads. * Fixed a race condition with joins on avplayer close that caused a crash. --- src/audio_core/sdl_audio.cpp | 8 +- src/audio_core/sdl_audio.h | 4 +- src/core/libraries/audio/audioout.cpp | 4 +- src/core/libraries/avplayer/avplayer.cpp | 67 +---- src/core/libraries/avplayer/avplayer.h | 1 + .../libraries/avplayer/avplayer_common.cpp | 59 ---- src/core/libraries/avplayer/avplayer_common.h | 90 +------ .../avplayer/avplayer_file_streamer.cpp | 6 +- src/core/libraries/avplayer/avplayer_impl.cpp | 43 +-- src/core/libraries/avplayer/avplayer_impl.h | 34 +-- .../libraries/avplayer/avplayer_source.cpp | 254 ++++++++---------- src/core/libraries/avplayer/avplayer_source.h | 25 +- .../libraries/avplayer/avplayer_state.cpp | 74 ++--- src/core/libraries/avplayer/avplayer_state.h | 21 +- src/core/libraries/kernel/thread_management.h | 16 +- src/core/libraries/kernel/time_management.h | 1 - src/core/libraries/save_data/savedata.cpp | 5 +- 17 files changed, 231 insertions(+), 481 deletions(-) diff --git a/src/audio_core/sdl_audio.cpp b/src/audio_core/sdl_audio.cpp index aac67d8c..9c8c5991 100644 --- a/src/audio_core/sdl_audio.cpp +++ b/src/audio_core/sdl_audio.cpp @@ -13,7 +13,7 @@ namespace Audio { int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq, Libraries::AudioOut::OrbisAudioOutParamFormat format) { using Libraries::AudioOut::OrbisAudioOutParamFormat; - std::scoped_lock lock{m_mutex}; + std::unique_lock lock{m_mutex}; for (int id = 0; id < portsOut.size(); id++) { auto& port = portsOut[id]; if (!port.isOpen) { @@ -88,7 +88,7 @@ int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq, } s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) { - std::scoped_lock lock{m_mutex}; + std::shared_lock lock{m_mutex}; auto& port = portsOut[handle - 1]; if (!port.isOpen) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; @@ -109,7 +109,7 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) { bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) { using Libraries::AudioOut::OrbisAudioOutParamFormat; - std::scoped_lock lock{m_mutex}; + std::shared_lock lock{m_mutex}; auto& port = portsOut[handle - 1]; if (!port.isOpen) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; @@ -147,7 +147,7 @@ bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) { } bool SDLAudio::AudioOutGetStatus(s32 handle, int* type, int* channels_num) { - std::scoped_lock lock{m_mutex}; + std::shared_lock lock{m_mutex}; auto& port = portsOut[handle - 1]; *type = port.type; *channels_num = port.channels_num; diff --git a/src/audio_core/sdl_audio.h b/src/audio_core/sdl_audio.h index d20c4455..7844bd61 100644 --- a/src/audio_core/sdl_audio.h +++ b/src/audio_core/sdl_audio.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include #include "core/libraries/audio/audioout.h" @@ -32,7 +32,7 @@ private: int volume[8] = {}; SDL_AudioStream* stream = nullptr; }; - std::mutex m_mutex; + std::shared_mutex m_mutex; std::array portsOut; // main up to 8 ports , BGM 1 port , voice up to 4 ports , // personal up to 4 ports , padspk up to 5 ports , aux 1 port }; diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index 08929383..cb676afc 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -234,7 +234,7 @@ int PS4_SYSV_ABI sceAudioOutGetSystemState() { } int PS4_SYSV_ABI sceAudioOutInit() { - LOG_INFO(Lib_AudioOut, "called"); + LOG_TRACE(Lib_AudioOut, "called"); if (audio != nullptr) { return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT; } @@ -323,12 +323,10 @@ 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 e8e99bca..8fccc659 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -11,8 +11,6 @@ #include // std::max, std::min -#include // va_list - namespace Libraries::AvPlayer { using namespace Kernel; @@ -140,17 +138,7 @@ SceAvPlayerHandle PS4_SYSV_ABI sceAvPlayerInit(SceAvPlayerInitData* data) { 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; - - return new AvPlayer(*data, priorities); + return new AvPlayer(*data); } s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data, @@ -176,56 +164,7 @@ s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data, 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; - - *p_player = new AvPlayer(data, priorities); + *p_player = new AvPlayer(data); return ORBIS_OK; } @@ -320,7 +259,7 @@ s32 PS4_SYSV_ABI sceAvPlayerStart(SceAvPlayerHandle handle) { } s32 PS4_SYSV_ABI sceAvPlayerStop(SceAvPlayerHandle handle) { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + LOG_TRACE(Lib_AvPlayer, "called"); if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } diff --git a/src/core/libraries/avplayer/avplayer.h b/src/core/libraries/avplayer/avplayer.h index e88419c1..360f06b6 100644 --- a/src/core/libraries/avplayer/avplayer.h +++ b/src/core/libraries/avplayer/avplayer.h @@ -5,6 +5,7 @@ #include "common/types.h" +#include // va_list #include // size_t namespace Core::Loader { diff --git a/src/core/libraries/avplayer/avplayer_common.cpp b/src/core/libraries/avplayer/avplayer_common.cpp index 3536c030..306603e2 100644 --- a/src/core/libraries/avplayer/avplayer_common.cpp +++ b/src/core/libraries/avplayer/avplayer_common.cpp @@ -12,65 +12,6 @@ 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)); diff --git a/src/core/libraries/avplayer/avplayer_common.h b/src/core/libraries/avplayer/avplayer_common.h index 5faf2388..a53696ec 100644 --- a/src/core/libraries/avplayer/avplayer_common.h +++ b/src/core/libraries/avplayer/avplayer_common.h @@ -31,9 +31,6 @@ enum class AvState { C0x0B, Buffering, Starting, - C0x0E, - C0x0F, - C0x10, Error, }; @@ -45,23 +42,6 @@ enum class AvEventType { 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 @@ -74,68 +54,9 @@ struct AvPlayerEvent { 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(); } @@ -161,19 +82,10 @@ public: } private: - PthreadMutex m_mutex{}; + std::mutex 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_file_streamer.cpp b/src/core/libraries/avplayer/avplayer_file_streamer.cpp index 43055fd5..4b1ddb6e 100644 --- a/src/core/libraries/avplayer/avplayer_file_streamer.cpp +++ b/src/core/libraries/avplayer/avplayer_file_streamer.cpp @@ -69,14 +69,14 @@ s64 AvPlayerFileStreamer::Seek(void* opaque, s64 offset, int whence) { if (whence == SEEK_CUR) { self->m_position = - std::min(u64(std::max(0ll, s64(self->m_position) + offset)), self->m_file_size); + std::min(u64(std::max(0i64, 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); + self->m_position = std::min(u64(std::max(0i64, 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); + std::min(u64(std::max(0i64, s64(self->m_file_size) + offset)), self->m_file_size); return self->m_position; } diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp index f08356bd..1114254f 100644 --- a/src/core/libraries/avplayer/avplayer_impl.cpp +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -77,29 +77,30 @@ u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { return size(ptr); } -AvPlayer::AvPlayer(const SceAvPlayerInitData& data, const ThreadPriorities& priorities) - : m_file_io_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_FileIO"), 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; +SceAvPlayerInitData AvPlayer::StubInitData(const SceAvPlayerInitData& data) { + SceAvPlayerInitData result = data; + result.memory_replacement.object_ptr = this; + result.memory_replacement.allocate = &AvPlayer::Allocate; + result.memory_replacement.deallocate = &AvPlayer::Deallocate; + result.memory_replacement.allocate_texture = &AvPlayer::AllocateTexture; + result.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 = {}; + result.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; + result.file_replacement.object_ptr = this; + result.file_replacement.open = &AvPlayer::OpenFile; + result.file_replacement.close = &AvPlayer::CloseFile; + result.file_replacement.readOffset = &AvPlayer::ReadOffsetFile; + result.file_replacement.size = &AvPlayer::SizeFile; } - - m_state = std::make_unique(m_init_data, priorities); + return result; } +AvPlayer::AvPlayer(const SceAvPlayerInitData& data) + : m_init_data(StubInitData(data)), m_init_data_original(data), + m_state(std::make_unique(m_init_data)) {} + s32 AvPlayer::PostInit(const SceAvPlayerPostInitData& data) { m_post_init_data = data; return ORBIS_OK; @@ -109,11 +110,9 @@ 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; } @@ -121,7 +120,11 @@ s32 AvPlayer::GetStreamCount() { if (m_state == nullptr) { return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; } - return m_state->GetStreamCount(); + const auto res = m_state->GetStreamCount(); + if (AVPLAYER_IS_ERROR(res)) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + return res; } s32 AvPlayer::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { diff --git a/src/core/libraries/avplayer/avplayer_impl.h b/src/core/libraries/avplayer/avplayer_impl.h index 98f959e6..df4acfee 100644 --- a/src/core/libraries/avplayer/avplayer_impl.h +++ b/src/core/libraries/avplayer/avplayer_impl.h @@ -9,6 +9,8 @@ #include "core/libraries/kernel/thread_management.h" +#include + extern "C" { #include #include @@ -21,19 +23,7 @@ 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(const SceAvPlayerInitData& data, const ThreadPriorities& priorities); + AvPlayer(const SceAvPlayerInitData& data); s32 PostInit(const SceAvPlayerPostInitData& data); s32 AddSource(std::string_view filename); @@ -51,13 +41,27 @@ public: private: using ScePthreadMutex = Kernel::ScePthreadMutex; - std::unique_ptr m_state{}; + // 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); + + SceAvPlayerInitData StubInitData(const SceAvPlayerInitData& data); + SceAvPlayerInitData m_init_data{}; SceAvPlayerInitData m_init_data_original{}; SceAvPlayerPostInitData m_post_init_data{}; - PthreadMutex m_file_io_mutex{}; + std::mutex m_file_io_mutex{}; std::atomic_bool m_has_source{}; + std::unique_ptr m_state{}; }; } // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index c3d3dc55..e235b2c2 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -24,9 +24,9 @@ namespace Libraries::AvPlayer { using namespace Kernel; AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state, std::string_view path, - const SceAvPlayerInitData& init_data, ThreadPriorities& priorities, + const SceAvPlayerInitData& init_data, SceAvPlayerSourceType source_type) - : m_state(state), m_priorities(priorities), m_memory_replacement(init_data.memory_replacement), + : m_state(state), m_memory_replacement(init_data.memory_replacement), m_num_output_video_framebuffers(init_data.num_output_video_framebuffers) { AVFormatContext* context = avformat_alloc_context(); if (init_data.file_replacement.open != nullptr) { @@ -113,7 +113,7 @@ s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) info.details.video.height = p_stream->codecpar->height; if (p_lang_node != nullptr) { std::memcpy(info.details.video.language_code, p_lang_node->value, - std::min(strlen(p_lang_node->value), 3ull)); + std::min(strlen(p_lang_node->value), size_t(3))); } break; case SCE_AVPLAYER_AUDIO: @@ -123,7 +123,7 @@ s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) info.details.audio.size = 0; // sceAvPlayerGetStreamInfo() is expected to set this to 0 if (p_lang_node != nullptr) { std::memcpy(info.details.audio.language_code, p_lang_node->value, - std::min(strlen(p_lang_node->value), 3ull)); + std::min(strlen(p_lang_node->value), size_t(3))); } break; case SCE_AVPLAYER_TIMEDTEXT: @@ -132,7 +132,7 @@ s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) info.details.subs.text_size = 12; if (p_lang_node != nullptr) { std::memcpy(info.details.subs.language_code, p_lang_node->value, - std::min(strlen(p_lang_node->value), 3ull)); + std::min(strlen(p_lang_node->value), size_t(3))); } break; default: @@ -189,7 +189,7 @@ bool AvPlayerSource::EnableStream(u32 stream_index) { const auto num_channels = m_audio_codec_context->ch_layout.nb_channels; const auto align = num_channels * sizeof(u16); const auto size = num_channels * sizeof(u16) * 1024; - for (u64 index = 0; index < 2; ++index) { + for (u64 index = 0; index < 4; ++index) { m_audio_buffers.Push(FrameBuffer(m_memory_replacement, 0x100, size)); } LOG_INFO(Lib_AvPlayer, "Audio stream {} enabled", stream_index); @@ -212,78 +212,41 @@ std::optional AvPlayerSource::HasFrames(u32 num_frames) { } s32 AvPlayerSource::Start() { + std::unique_lock lock(m_state_mutex); + if (m_audio_codec_context == nullptr && m_video_codec_context == nullptr) { LOG_ERROR(Lib_AvPlayer, "Could not start playback. NULL context."); 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) { - LOG_ERROR(Lib_AvPlayer, "Could not create DEMUXER thread."); - 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) { - LOG_ERROR(Lib_AvPlayer, "Could not create VIDEO DECODER thread."); - 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) { - LOG_ERROR(Lib_AvPlayer, "Could not create AUDIO DECODER thread."); - return -1; - } - } + m_demuxer_thread = std::jthread([this](std::stop_token stop) { this->DemuxerThread(stop); }); + m_video_decoder_thread = + std::jthread([this](std::stop_token stop) { this->VideoDecoderThread(stop); }); + m_audio_decoder_thread = + std::jthread([this](std::stop_token stop) { this->AudioDecoderThread(stop); }); m_start_time = std::chrono::high_resolution_clock::now(); return 0; } bool AvPlayerSource::Stop() { - if (m_is_stop) { + std::unique_lock lock(m_state_mutex); + + if (!HasRunningThreads()) { LOG_WARNING(Lib_AvPlayer, "Could not stop playback: already stopped."); return false; } - m_is_stop = true; - - void* res = nullptr; - if (m_video_decoder_thread != nullptr) { - scePthreadJoin(m_video_decoder_thread, &res); + m_video_decoder_thread.request_stop(); + m_audio_decoder_thread.request_stop(); + m_demuxer_thread.request_stop(); + if (m_demuxer_thread.joinable()) { + m_demuxer_thread.join(); } - if (m_audio_decoder_thread != nullptr) { - scePthreadJoin(m_audio_decoder_thread, &res); + if (m_video_decoder_thread.joinable()) { + m_video_decoder_thread.join(); } - if (m_demuxer_thread != nullptr) { - scePthreadJoin(m_demuxer_thread, &res); + if (m_audio_decoder_thread.joinable()) { + m_audio_decoder_thread.join(); } - m_audio_packets.Clear(); - m_video_packets.Clear(); - m_audio_frames.Clear(); - m_video_frames.Clear(); if (m_current_audio_frame.has_value()) { m_audio_buffers.Push(std::move(m_current_audio_frame.value())); m_current_audio_frame.reset(); @@ -292,10 +255,19 @@ bool AvPlayerSource::Stop() { m_video_buffers.Push(std::move(m_current_video_frame.value())); m_current_video_frame.reset(); } + + m_audio_packets.Clear(); + m_video_packets.Clear(); + m_audio_frames.Clear(); + m_video_frames.Clear(); return true; } bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfo& video_info) { + if (!IsActive()) { + return false; + } + SceAvPlayerFrameInfoEx info{}; if (!GetVideoData(info)) { return false; @@ -315,8 +287,13 @@ static void CopyNV12Data(u8* dst, const AVFrame& src) { } bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { + if (!IsActive()) { + return false; + } + + using namespace std::chrono; while (m_video_frames.Size() == 0 && !m_is_eof) { - sceKernelUsleep(5000); + std::this_thread::sleep_for(milliseconds(5)); } auto frame = m_video_frames.Pop(); @@ -326,11 +303,10 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { } { - using namespace std::chrono; auto elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); while (elapsed_time < frame->info.timestamp) { - sceKernelUsleep((frame->info.timestamp - elapsed_time) * 1000); + std::this_thread::sleep_for(milliseconds(frame->info.timestamp - elapsed_time)); elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); } @@ -346,8 +322,13 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { } bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { + if (!IsActive()) { + return false; + } + + using namespace std::chrono; while (m_audio_frames.Size() == 0 && !m_is_eof) { - sceKernelUsleep(5000); + std::this_thread::sleep_for(milliseconds(5)); } auto frame = m_audio_frames.Pop(); @@ -357,11 +338,10 @@ bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { } { - using namespace std::chrono; auto elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); while (elapsed_time < frame->info.timestamp) { - sceKernelUsleep((frame->info.timestamp - elapsed_time) * 1000); + std::this_thread::sleep_for(milliseconds(frame->info.timestamp - elapsed_time)); elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); } @@ -387,8 +367,8 @@ u64 AvPlayerSource::CurrentTime() { } 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); + return !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) { @@ -427,52 +407,51 @@ void AvPlayerSource::ReleaseAVFormatContext(AVFormatContext* context) { } } -void* AvPlayerSource::DemuxerThread(void* opaque) { - const auto self = reinterpret_cast(opaque); - if (!self->m_audio_stream_index.has_value() && !self->m_video_stream_index.has_value()) { +void AvPlayerSource::DemuxerThread(std::stop_token stop) { + using namespace std::chrono; + if (!m_audio_stream_index.has_value() && !m_video_stream_index.has_value()) { LOG_WARNING(Lib_AvPlayer, "Could not start DEMUXER thread. No streams enabled."); - return nullptr; + return; } LOG_INFO(Lib_AvPlayer, "Demuxer Thread started"); - while (!self->m_is_stop) { - if (self->m_video_packets.Size() > 60) { - sceKernelUsleep(5000); + while (!stop.stop_requested()) { + if (m_video_packets.Size() > 30 && m_audio_packets.Size() > 8) { + std::this_thread::sleep_for(milliseconds(5)); continue; } AVPacketPtr up_packet(av_packet_alloc(), &ReleaseAVPacket); - const auto res = av_read_frame(self->m_avformat_context.get(), up_packet.get()); + const auto res = av_read_frame(m_avformat_context.get(), up_packet.get()); if (res < 0) { if (res == AVERROR_EOF) { LOG_INFO(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); + m_state.OnError(); + return; } 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)); + if (up_packet->stream_index == m_video_stream_index) { + m_video_packets.Push(std::move(up_packet)); + } else if (up_packet->stream_index == m_audio_stream_index) { + m_audio_packets.Push(std::move(up_packet)); } } - self->m_is_eof = true; + m_is_eof = true; void* res; - if (self->m_video_decoder_thread) { - scePthreadJoin(self->m_video_decoder_thread, &res); + if (m_video_decoder_thread.joinable()) { + m_video_decoder_thread.join(); } - if (self->m_audio_decoder_thread) { - scePthreadJoin(self->m_audio_decoder_thread, &res); + if (m_audio_decoder_thread.joinable()) { + m_audio_decoder_thread.join(); } - self->m_state.OnEOF(); + m_state.OnEOF(); LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normaly"); - scePthreadExit(0); } AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& frame) { @@ -537,67 +516,63 @@ Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame }; } -void* AvPlayerSource::VideoDecoderThread(void* opaque) { +void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { + using namespace std::chrono; LOG_INFO(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_packets.Size() == 0) { - sceKernelUsleep(5000); + while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) { + if (m_video_packets.Size() == 0) { + std::this_thread::sleep_for(milliseconds(5)); continue; } - const auto packet = self->m_video_packets.Pop(); + const auto packet = m_video_packets.Pop(); if (!packet.has_value()) { continue; } - auto res = avcodec_send_packet(self->m_video_codec_context.get(), packet->get()); + auto res = avcodec_send_packet(m_video_codec_context.get(), packet->get()); if (res < 0 && res != AVERROR(EAGAIN)) { - self->m_state.OnError(); + m_state.OnError(); LOG_ERROR(Lib_AvPlayer, "Could not send packet to the video codec. Error = {}", av_err2str(res)); - scePthreadExit(nullptr); + return; } while (res >= 0) { - if (self->m_video_buffers.Size() == 0 && !self->m_is_stop) { - sceKernelUsleep(5000); + if (m_video_buffers.Size() == 0 && !stop.stop_requested()) { + std::this_thread::sleep_for(milliseconds(5)); continue; } auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); - res = avcodec_receive_frame(self->m_video_codec_context.get(), up_frame.get()); + res = avcodec_receive_frame(m_video_codec_context.get(), up_frame.get()); if (res < 0) { if (res == AVERROR_EOF) { LOG_INFO(Lib_AvPlayer, "EOF reached in video decoder"); - scePthreadExit(nullptr); + return; } 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); + m_state.OnError(); + return; } } else { - auto buffer = self->m_video_buffers.Pop(); + auto buffer = m_video_buffers.Pop(); if (!buffer.has_value()) { // Video buffers queue was cleared. This means that player was stopped. break; } if (up_frame->format != AV_PIX_FMT_NV12) { - const auto nv12_frame = self->ConvertVideoFrame(*up_frame); - self->m_video_frames.Push( - self->PrepareVideoFrame(std::move(buffer.value()), *nv12_frame)); + const auto nv12_frame = ConvertVideoFrame(*up_frame); + m_video_frames.Push(PrepareVideoFrame(std::move(buffer.value()), *nv12_frame)); } else { - self->m_video_frames.Push( - self->PrepareVideoFrame(std::move(buffer.value()), *up_frame)); + m_video_frames.Push(PrepareVideoFrame(std::move(buffer.value()), *up_frame)); } LOG_TRACE(Lib_AvPlayer, "Produced Video Frame. Num Frames: {}", - self->m_video_frames.Size()); + m_video_frames.Size()); } } } LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normaly"); - scePthreadExit(nullptr); } AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& frame) { @@ -659,66 +634,67 @@ Frame AvPlayerSource::PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame }; } -void* AvPlayerSource::AudioDecoderThread(void* opaque) { +void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { + using namespace std::chrono; LOG_INFO(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_packets.Size() == 0) { - sceKernelUsleep(5000); + while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) { + if (m_audio_packets.Size() == 0) { + std::this_thread::sleep_for(milliseconds(5)); continue; } - const auto packet = self->m_audio_packets.Pop(); + const auto packet = m_audio_packets.Pop(); if (!packet.has_value()) { continue; } - auto res = avcodec_send_packet(self->m_audio_codec_context.get(), packet->get()); + auto res = avcodec_send_packet(m_audio_codec_context.get(), packet->get()); if (res < 0 && res != AVERROR(EAGAIN)) { - self->m_state.OnError(); + m_state.OnError(); LOG_ERROR(Lib_AvPlayer, "Could not send packet to the audio codec. Error = {}", av_err2str(res)); - scePthreadExit(nullptr); + return; } while (res >= 0) { - if (self->m_audio_buffers.Size() == 0 && !self->m_is_stop) { - sceKernelUsleep(5000); + if (m_audio_buffers.Size() == 0 && !stop.stop_requested()) { + std::this_thread::sleep_for(milliseconds(5)); continue; } auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); - res = avcodec_receive_frame(self->m_audio_codec_context.get(), up_frame.get()); + res = avcodec_receive_frame(m_audio_codec_context.get(), up_frame.get()); if (res < 0) { if (res == AVERROR_EOF) { LOG_INFO(Lib_AvPlayer, "EOF reached in audio decoder"); - scePthreadExit(nullptr); + return; } else if (res != AVERROR(EAGAIN)) { - self->m_state.OnError(); + m_state.OnError(); LOG_ERROR(Lib_AvPlayer, "Could not receive frame from the audio codec. Error = {}", av_err2str(res)); - scePthreadExit(nullptr); + return; } } else { - auto buffer = self->m_audio_buffers.Pop(); + auto buffer = m_audio_buffers.Pop(); if (!buffer.has_value()) { // Audio buffers queue was cleared. This means that player was stopped. break; } if (up_frame->format != AV_SAMPLE_FMT_S16) { - const auto pcm16_frame = self->ConvertAudioFrame(*up_frame); - self->m_audio_frames.Push( - self->PrepareAudioFrame(std::move(buffer.value()), *pcm16_frame)); + const auto pcm16_frame = ConvertAudioFrame(*up_frame); + m_audio_frames.Push(PrepareAudioFrame(std::move(buffer.value()), *pcm16_frame)); } else { - self->m_audio_frames.Push( - self->PrepareAudioFrame(std::move(buffer.value()), *up_frame)); + m_audio_frames.Push(PrepareAudioFrame(std::move(buffer.value()), *up_frame)); } LOG_TRACE(Lib_AvPlayer, "Produced Audio Frame. Num Frames: {}", - self->m_audio_frames.Size()); + m_audio_frames.Size()); } } } LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normaly"); - scePthreadExit(nullptr); +} + +bool AvPlayerSource::HasRunningThreads() const { + return m_demuxer_thread.joinable() || m_video_decoder_thread.joinable() || + m_audio_decoder_thread.joinable(); } } // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h index 67ad44d8..c2f9ac02 100644 --- a/src/core/libraries/avplayer/avplayer_source.h +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -12,8 +12,11 @@ #include #include +#include #include +#include #include +#include struct AVCodecContext; struct AVFormatContext; @@ -88,8 +91,7 @@ struct Frame { class AvPlayerSource { public: AvPlayerSource(AvPlayerStateCallback& state, std::string_view path, - const SceAvPlayerInitData& init_data, ThreadPriorities& priorities, - SceAvPlayerSourceType source_type); + const SceAvPlayerInitData& init_data, SceAvPlayerSourceType source_type); ~AvPlayerSource(); bool FindStreamInfo(); @@ -109,10 +111,6 @@ public: 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); @@ -127,6 +125,12 @@ private: using SWSContextPtr = std::unique_ptr; using AVFormatContextPtr = std::unique_ptr; + void DemuxerThread(std::stop_token stop); + void VideoDecoderThread(std::stop_token stop); + void AudioDecoderThread(std::stop_token stop); + + bool HasRunningThreads() const; + AVFramePtr ConvertAudioFrame(const AVFrame& frame); AVFramePtr ConvertVideoFrame(const AVFrame& frame); @@ -135,13 +139,11 @@ private: AvPlayerStateCallback& m_state; - ThreadPriorities m_priorities{}; SceAvPlayerMemAllocator m_memory_replacement{}; u64 m_num_output_video_framebuffers{}; 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; @@ -160,9 +162,10 @@ private: 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{}; + std::mutex m_state_mutex; + std::jthread m_demuxer_thread{}; + std::jthread m_video_decoder_thread{}; + std::jthread m_audio_decoder_thread{}; AVFormatContextPtr m_avformat_context{nullptr, &ReleaseAVFormatContext}; AVCodecContextPtr m_video_codec_context{nullptr, &ReleaseAVCodecContext}; diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index e7aa4571..061d1da6 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -89,12 +89,8 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_i } // Called inside GAME thread -AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data, - const ThreadPriorities& priorities) - : m_init_data(init_data), m_event_replacement(init_data.event_replacement), - m_thread_priorities(priorities), - m_event_handler_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_EventHandler"), - m_state_machine_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_StateMachine") { +AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data) + : m_init_data(init_data), m_event_replacement(init_data.event_replacement) { if (m_event_replacement.event_callback == nullptr || init_data.auto_start) { m_auto_start = true; m_init_data.event_replacement.event_callback = &AvPlayerState::AutoPlayEventCallback; @@ -111,10 +107,9 @@ AvPlayerState::~AvPlayerState() { if (m_up_source && m_current_state == AvState::Play) { m_up_source->Stop(); } - m_quit = true; - if (m_controller_thread != nullptr) { - void* res = nullptr; - scePthreadJoin(m_controller_thread, &res); + if (m_controller_thread.joinable()) { + m_controller_thread.request_stop(); + m_controller_thread.join(); } m_event_queue.Clear(); } @@ -131,8 +126,7 @@ s32 AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType source return -1; } - m_up_source = std::make_unique(*this, path, m_init_data, m_thread_priorities, - source_type); + m_up_source = std::make_unique(*this, path, m_init_data, source_type); AddSourceEvent(); return 0; } @@ -166,17 +160,17 @@ s32 AvPlayerState::Start() { return 0; } -void* AvPlayerState::AvControllerThread(void* p_user_data) { - AvPlayerState* self = reinterpret_cast(p_user_data); - while (!self->m_quit) { - if (self->m_event_queue.Size() != 0) { - self->ProcessEvent(); +void AvPlayerState::AvControllerThread(std::stop_token stop) { + using std::chrono::milliseconds; + + while (!stop.stop_requested()) { + if (m_event_queue.Size() != 0) { + ProcessEvent(); continue; } - sceKernelUsleep(5000); - self->UpdateBufferingState(); + std::this_thread::sleep_for(milliseconds(5)); + UpdateBufferingState(); } - scePthreadExit(0); } // Called inside GAME thread @@ -198,22 +192,9 @@ void AvPlayerState::WarningEvent(s32 id) { } // 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) { - LOG_ERROR(Lib_AvPlayer, "Could not create CONTROLLER thread."); - return -1; - } - return 0; +void AvPlayerState::StartControllerThread() { + m_controller_thread = + std::jthread([this](std::stop_token stop) { this->AvControllerThread(stop); }); } // Called inside GAME thread @@ -262,7 +243,7 @@ bool AvPlayerState::IsActive() { return false; } return m_current_state != AvState::Stop && m_current_state != AvState::Error && - m_up_source->IsActive(); + m_current_state != AvState::EndOfFile && m_up_source->IsActive(); } u64 AvPlayerState::CurrentTime() { @@ -284,7 +265,9 @@ void AvPlayerState::OnError() { OnPlaybackStateChanged(AvState::Error); } -void AvPlayerState::OnEOF() {} +void AvPlayerState::OnEOF() { + SetState(AvState::EndOfFile); +} // Called inside CONTROLLER thread void AvPlayerState::OnPlaybackStateChanged(AvState state) { @@ -347,16 +330,16 @@ void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) { } // Called inside CONTROLLER thread -int AvPlayerState::ProcessEvent() { +void AvPlayerState::ProcessEvent() { if (m_current_state == AvState::Jump) { - return -2; + return; } std::lock_guard guard(m_event_handler_mutex); auto event = m_event_queue.Pop(); if (!event.has_value()) { - return -1; + return; } switch (event->event) { case AvEventType::WarningId: { @@ -385,16 +368,14 @@ int AvPlayerState::ProcessEvent() { default: break; } - - return 0; } // Called inside CONTROLLER thread -int AvPlayerState::UpdateBufferingState() { +void AvPlayerState::UpdateBufferingState() { if (m_current_state == AvState::Buffering) { const auto has_frames = OnBufferingCheckEvent(10); if (!has_frames.has_value()) { - return -1; + return; } if (has_frames.value()) { const auto state = @@ -405,14 +386,13 @@ int AvPlayerState::UpdateBufferingState() { } else if (m_current_state == AvState::Play) { const auto has_frames = OnBufferingCheckEvent(0); if (!has_frames.has_value()) { - return -1; + return; } if (!has_frames.value()) { SetState(AvState::Buffering); OnPlaybackStateChanged(AvState::Buffering); } } - return 0; } bool AvPlayerState::IsStateTransitionValid(AvState state) { diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h index cfddab09..8aacc575 100644 --- a/src/core/libraries/avplayer/avplayer_state.h +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -10,6 +10,9 @@ #include "core/libraries/kernel/thread_management.h" #include +#include +#include +#include namespace Libraries::AvPlayer { @@ -18,7 +21,7 @@ class AvDecoder; class AvPlayerState : public AvPlayerStateCallback { public: - AvPlayerState(const SceAvPlayerInitData& init_data, const ThreadPriorities& priorities); + AvPlayerState(const SceAvPlayerInitData& init_data); ~AvPlayerState(); s32 AddSource(std::string_view filename, SceAvPlayerSourceType source_type); @@ -51,34 +54,32 @@ private: void EmitEvent(SceAvPlayerEvents event_id, void* event_data = nullptr); bool SetState(AvState state); - static void* PS4_SYSV_ABI AvControllerThread(void* p_user_data); + void AvControllerThread(std::stop_token stop); void AddSourceEvent(); void WarningEvent(s32 id); - int StartControllerThread(); - int ProcessEvent(); - int UpdateBufferingState(); + void StartControllerThread(); + void ProcessEvent(); + void UpdateBufferingState(); bool IsStateTransitionValid(AvState state); std::unique_ptr m_up_source; SceAvPlayerInitData m_init_data{}; SceAvPlayerEventReplacement m_event_replacement{}; - ThreadPriorities m_thread_priorities{}; bool m_auto_start{}; u8 m_default_language[4]{}; - std::atomic_bool 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{}; + std::mutex m_state_machine_mutex{}; + std::mutex m_event_handler_mutex{}; + std::jthread m_controller_thread{}; AvPlayerQueue m_event_queue{}; }; diff --git a/src/core/libraries/kernel/thread_management.h b/src/core/libraries/kernel/thread_management.h index bd555ccc..c5935275 100644 --- a/src/core/libraries/kernel/thread_management.h +++ b/src/core/libraries/kernel/thread_management.h @@ -158,24 +158,19 @@ 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); /*** @@ -183,14 +178,11 @@ 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 8934171d..a28f8c13 100644 --- a/src/core/libraries/kernel/time_management.h +++ b/src/core/libraries/kernel/time_management.h @@ -50,7 +50,6 @@ 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); diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 64237994..98ff4fdd 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -792,10 +792,11 @@ int PS4_SYSV_ABI sceSaveDataTransferringMount() { } s32 PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { - LOG_INFO(Lib_SaveData, "mountPoint = {}", std::string(mountPoint->data)); - if (std::string(mountPoint->data).empty()) { + if (mountPoint->data == nullptr) { + LOG_WARNING(Lib_SaveData, "mountPoint = nullptr"); return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; } + LOG_INFO(Lib_SaveData, "mountPoint = {}", mountPoint->data); const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(1) / game_serial / mountPoint->data; auto* mnt = Common::Singleton::Instance(); From 5c4ac98d499ac10392dfda4e4559c0db659a1fb5 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Wed, 14 Aug 2024 22:00:01 +0300 Subject: [PATCH 4/6] fixing build on linux and mac --- src/audio_core/sdl_audio.cpp | 10 +++++++--- src/core/libraries/avplayer/avplayer.cpp | 7 ------- src/core/libraries/avplayer/avplayer_file_streamer.cpp | 6 +++--- src/core/libraries/avplayer/avplayer_source.h | 3 +-- src/core/libraries/avplayer/avplayer_state.h | 3 +-- src/core/libraries/save_data/savedata.cpp | 5 ++--- 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/audio_core/sdl_audio.cpp b/src/audio_core/sdl_audio.cpp index 9c8c5991..f544c52f 100644 --- a/src/audio_core/sdl_audio.cpp +++ b/src/audio_core/sdl_audio.cpp @@ -1,12 +1,16 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "sdl_audio.h" + +#include "common/assert.h" +#include "core/libraries/error_codes.h" + #include #include #include -#include "common/assert.h" -#include "core/libraries/error_codes.h" -#include "sdl_audio.h" + +#include // std::unique_lock namespace Audio { diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index 8fccc659..8a1152a3 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -9,8 +9,6 @@ #include "core/libraries/kernel/thread_management.h" #include "core/libraries/libs.h" -#include // std::max, std::min - namespace Libraries::AvPlayer { using namespace Kernel; @@ -119,11 +117,6 @@ bool PS4_SYSV_ABI sceAvPlayerGetVideoDataEx(SceAvPlayerHandle handle, 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) { diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.cpp b/src/core/libraries/avplayer/avplayer_file_streamer.cpp index 4b1ddb6e..dc1386a4 100644 --- a/src/core/libraries/avplayer/avplayer_file_streamer.cpp +++ b/src/core/libraries/avplayer/avplayer_file_streamer.cpp @@ -69,14 +69,14 @@ s64 AvPlayerFileStreamer::Seek(void* opaque, s64 offset, int whence) { if (whence == SEEK_CUR) { self->m_position = - std::min(u64(std::max(0i64, s64(self->m_position) + offset)), self->m_file_size); + std::min(u64(std::max(s64(0), 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(0i64, offset)), self->m_file_size); + self->m_position = std::min(u64(std::max(s64(0), offset)), self->m_file_size); return self->m_position; } else if (whence == SEEK_END) { self->m_position = - std::min(u64(std::max(0i64, s64(self->m_file_size) + offset)), self->m_file_size); + std::min(u64(std::max(s64(0), s64(self->m_file_size) + offset)), self->m_file_size); return self->m_position; } diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h index c2f9ac02..b3fbb30d 100644 --- a/src/core/libraries/avplayer/avplayer_source.h +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -8,15 +8,14 @@ #include "avplayer_data_streamer.h" #include "common/types.h" +#include "common/polyfill_thread.h" #include "core/libraries/kernel/thread_management.h" #include #include #include #include -#include #include -#include struct AVCodecContext; struct AVFormatContext; diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h index 8aacc575..eed3265f 100644 --- a/src/core/libraries/avplayer/avplayer_state.h +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -7,12 +7,11 @@ #include "avplayer_data_streamer.h" #include "avplayer_source.h" +#include "common/polyfill_thread.h" #include "core/libraries/kernel/thread_management.h" #include #include -#include -#include namespace Libraries::AvPlayer { diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 98ff4fdd..20496d76 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -792,11 +792,10 @@ int PS4_SYSV_ABI sceSaveDataTransferringMount() { } s32 PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { - if (mountPoint->data == nullptr) { - LOG_WARNING(Lib_SaveData, "mountPoint = nullptr"); + LOG_INFO(Lib_SaveData, "mountPoint = {}", mountPoint->data); + if (std::string_view(mountPoint->data).empty()) { return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; } - LOG_INFO(Lib_SaveData, "mountPoint = {}", mountPoint->data); const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(1) / game_serial / mountPoint->data; auto* mnt = Common::Singleton::Instance(); From b3ef959b25a25f4657fc761d0ca27dde50dbfec5 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Thu, 15 Aug 2024 21:59:59 +0300 Subject: [PATCH 5/6] Fixed threading, migrated to CVs, added looping --- .gitmodules | 2 +- src/core/libraries/avplayer/avplayer.cpp | 7 +- src/core/libraries/avplayer/avplayer_impl.cpp | 7 ++ src/core/libraries/avplayer/avplayer_impl.h | 1 + .../libraries/avplayer/avplayer_source.cpp | 103 +++++++++++------- src/core/libraries/avplayer/avplayer_source.h | 47 +++++++- .../libraries/avplayer/avplayer_state.cpp | 40 +++++-- src/core/libraries/avplayer/avplayer_state.h | 3 + 8 files changed, 161 insertions(+), 49 deletions(-) diff --git a/.gitmodules b/.gitmodules index e96f33ec..60fb5fbb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -63,4 +63,4 @@ url = https://github.com/HowardHinnant/date.git [submodule "externals/ffmpeg-core"] path = externals/ffmpeg-core - url = https://github.com/RPCS3/ffmpeg-core.git + url = https://github.com/shadps4-emu/ext-ffmpeg-core diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index 8a1152a3..78e03d78 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -230,6 +230,9 @@ s32 PS4_SYSV_ABI sceAvPlayerSetLooping(SceAvPlayerHandle handle, bool loop_flag) if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } + if (!handle->SetLooping(loop_flag)) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } return ORBIS_OK; } @@ -256,7 +259,9 @@ s32 PS4_SYSV_ABI sceAvPlayerStop(SceAvPlayerHandle handle) { if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } - return handle->Stop(); + const auto res = handle->Stop(); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; } s32 PS4_SYSV_ABI sceAvPlayerStreamCount(SceAvPlayerHandle handle) { diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp index 1114254f..cdfff827 100644 --- a/src/core/libraries/avplayer/avplayer_impl.cpp +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -190,4 +190,11 @@ s32 AvPlayer::Stop() { return ORBIS_OK; } +bool AvPlayer::SetLooping(bool is_looping) { + if (m_state == nullptr) { + return false; + } + return m_state->SetLooping(is_looping); +} + } // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_impl.h b/src/core/libraries/avplayer/avplayer_impl.h index df4acfee..09989d39 100644 --- a/src/core/libraries/avplayer/avplayer_impl.h +++ b/src/core/libraries/avplayer/avplayer_impl.h @@ -37,6 +37,7 @@ public: bool IsActive(); u64 CurrentTime(); s32 Stop(); + bool SetLooping(bool is_looping); private: using ScePthreadMutex = Kernel::ScePthreadMutex; diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index e235b2c2..964e0fbd 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -27,7 +27,8 @@ AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state, std::string_view pa const SceAvPlayerInitData& init_data, SceAvPlayerSourceType source_type) : m_state(state), m_memory_replacement(init_data.memory_replacement), - m_num_output_video_framebuffers(init_data.num_output_video_framebuffers) { + m_num_output_video_framebuffers( + std::min(std::max(2, init_data.num_output_video_framebuffers), 16)) { AVFormatContext* context = avformat_alloc_context(); if (init_data.file_replacement.open != nullptr) { m_up_data_streamer = @@ -208,7 +209,7 @@ void AvPlayerSource::SetLooping(bool is_looping) { } std::optional AvPlayerSource::HasFrames(u32 num_frames) { - return m_video_frames.Size() > num_frames || m_is_eof; + return m_video_packets.Size() > num_frames || m_is_eof; } s32 AvPlayerSource::Start() { @@ -255,6 +256,7 @@ bool AvPlayerSource::Stop() { m_video_buffers.Push(std::move(m_current_video_frame.value())); m_current_video_frame.reset(); } + m_stop_cv.Notify(); m_audio_packets.Clear(); m_video_packets.Clear(); @@ -291,30 +293,30 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { return false; } - using namespace std::chrono; - while (m_video_frames.Size() == 0 && !m_is_eof) { - std::this_thread::sleep_for(milliseconds(5)); - } + m_video_frames_cv.Wait([this]() { return m_video_frames.Size() != 0 || m_is_eof; }); auto frame = m_video_frames.Pop(); if (!frame.has_value()) { - LOG_WARNING(Lib_AvPlayer, "Could get video frame: no frames."); + LOG_WARNING(Lib_AvPlayer, "Could get video frame. EOF reached."); return false; } { + using namespace std::chrono; auto elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); - while (elapsed_time < frame->info.timestamp) { - std::this_thread::sleep_for(milliseconds(frame->info.timestamp - elapsed_time)); - elapsed_time = - duration_cast(high_resolution_clock::now() - m_start_time).count(); + if (elapsed_time < frame->info.timestamp) { + if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time), + [&]() { return elapsed_time >= frame->info.timestamp; })) { + return false; + } } } // return the buffer to the queue if (m_current_video_frame.has_value()) { m_video_buffers.Push(std::move(m_current_video_frame.value())); + m_video_buffers_cv.Notify(); } m_current_video_frame = std::move(frame->buffer); video_info = frame->info; @@ -326,30 +328,30 @@ bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { return false; } - using namespace std::chrono; - while (m_audio_frames.Size() == 0 && !m_is_eof) { - std::this_thread::sleep_for(milliseconds(5)); - } + m_audio_frames_cv.Wait([this]() { return m_audio_frames.Size() != 0 || m_is_eof; }); auto frame = m_audio_frames.Pop(); if (!frame.has_value()) { - LOG_WARNING(Lib_AvPlayer, "Could get audio frame: no frames."); + LOG_WARNING(Lib_AvPlayer, "Could get audio frame. EOF reached."); return false; } { + using namespace std::chrono; auto elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); - while (elapsed_time < frame->info.timestamp) { - std::this_thread::sleep_for(milliseconds(frame->info.timestamp - elapsed_time)); - elapsed_time = - duration_cast(high_resolution_clock::now() - m_start_time).count(); + if (elapsed_time < frame->info.timestamp) { + if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time), + [&]() { return elapsed_time >= frame->info.timestamp; })) { + return false; + } } } // return the buffer to the queue if (m_current_audio_frame.has_value()) { m_audio_buffers.Push(std::move(m_current_audio_frame.value())); + m_audio_buffers_cv.Notify(); } m_current_audio_frame = std::move(frame->buffer); @@ -424,8 +426,26 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) { const auto res = av_read_frame(m_avformat_context.get(), up_packet.get()); if (res < 0) { if (res == AVERROR_EOF) { - LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer"); - break; + if (m_is_looping) { + LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer. Looping the source..."); + avio_seek(m_avformat_context->pb, 0, SEEK_SET); + if (m_video_stream_index.has_value()) { + const auto index = m_video_stream_index.value(); + const auto stream = m_avformat_context->streams[index]; + avformat_seek_file(m_avformat_context.get(), index, 0, 0, stream->duration, + 0); + } + if (m_audio_stream_index.has_value()) { + const auto index = m_audio_stream_index.value(); + const auto stream = m_avformat_context->streams[index]; + avformat_seek_file(m_avformat_context.get(), index, 0, 0, stream->duration, + 0); + } + continue; + } else { + LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer. Exiting."); + break; + } } else { LOG_ERROR(Lib_AvPlayer, "Could not read AV frame: error = {}", res); m_state.OnError(); @@ -435,14 +455,20 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) { } if (up_packet->stream_index == m_video_stream_index) { m_video_packets.Push(std::move(up_packet)); + m_video_packets_cv.Notify(); } else if (up_packet->stream_index == m_audio_stream_index) { m_audio_packets.Push(std::move(up_packet)); + m_audio_packets_cv.Notify(); } } m_is_eof = true; - void* res; + m_video_packets_cv.Notify(); + m_audio_packets_cv.Notify(); + m_video_frames_cv.Notify(); + m_audio_frames_cv.Notify(); + if (m_video_decoder_thread.joinable()) { m_video_decoder_thread.join(); } @@ -457,7 +483,7 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) { 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->pkt_dts = frame.pkt_dts < 0 ? 0 : frame.pkt_dts; nv12_frame->format = AV_PIX_FMT_NV12; nv12_frame->width = frame.width; nv12_frame->height = frame.height; @@ -520,8 +546,8 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { using namespace std::chrono; LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started"); while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) { - if (m_video_packets.Size() == 0) { - std::this_thread::sleep_for(milliseconds(5)); + if (!m_video_packets_cv.Wait( + stop, [this]() { return m_video_packets.Size() != 0 || m_is_eof; })) { continue; } const auto packet = m_video_packets.Pop(); @@ -537,8 +563,10 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { return; } while (res >= 0) { - if (m_video_buffers.Size() == 0 && !stop.stop_requested()) { - std::this_thread::sleep_for(milliseconds(5)); + if (!m_video_buffers_cv.Wait(stop, [this]() { return m_video_buffers.Size() != 0; })) { + break; + } + if (m_video_buffers.Size() == 0) { continue; } auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); @@ -566,8 +594,7 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { } else { m_video_frames.Push(PrepareVideoFrame(std::move(buffer.value()), *up_frame)); } - LOG_TRACE(Lib_AvPlayer, "Produced Video Frame. Num Frames: {}", - m_video_frames.Size()); + m_video_frames_cv.Notify(); } } } @@ -578,7 +605,7 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { 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->pkt_dts = frame.pkt_dts < 0 ? 0 : frame.pkt_dts; pcm16_frame->format = AV_SAMPLE_FMT_S16; pcm16_frame->ch_layout = frame.ch_layout; pcm16_frame->sample_rate = frame.sample_rate; @@ -638,8 +665,8 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { using namespace std::chrono; LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started"); while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) { - if (m_audio_packets.Size() == 0) { - std::this_thread::sleep_for(milliseconds(5)); + if (!m_audio_packets_cv.Wait( + stop, [this]() { return m_audio_packets.Size() != 0 || m_is_eof; })) { continue; } const auto packet = m_audio_packets.Pop(); @@ -654,10 +681,13 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { return; } while (res >= 0) { - if (m_audio_buffers.Size() == 0 && !stop.stop_requested()) { - std::this_thread::sleep_for(milliseconds(5)); + if (!m_audio_buffers_cv.Wait(stop, [this]() { return m_audio_buffers.Size() != 0; })) { + break; + } + if (m_audio_buffers.Size() == 0) { continue; } + auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); res = avcodec_receive_frame(m_audio_codec_context.get(), up_frame.get()); if (res < 0) { @@ -683,8 +713,7 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { } else { m_audio_frames.Push(PrepareAudioFrame(std::move(buffer.value()), *up_frame)); } - LOG_TRACE(Lib_AvPlayer, "Produced Audio Frame. Num Frames: {}", - m_audio_frames.Size()); + m_audio_frames_cv.Notify(); } } } diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h index b3fbb30d..7144e7ee 100644 --- a/src/core/libraries/avplayer/avplayer_source.h +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -7,12 +7,13 @@ #include "avplayer_common.h" #include "avplayer_data_streamer.h" -#include "common/types.h" #include "common/polyfill_thread.h" +#include "common/types.h" #include "core/libraries/kernel/thread_management.h" #include #include +#include #include #include #include @@ -87,6 +88,36 @@ struct Frame { SceAvPlayerFrameInfoEx info; }; +class EventCV { +public: + template + void Wait(Pred pred) { + std::unique_lock lock(m_mutex); + m_cv.wait(lock, std::move(pred)); + } + + template + bool Wait(std::stop_token stop, Pred pred) { + std::unique_lock lock(m_mutex); + return m_cv.wait(lock, std::move(stop), std::move(pred)); + } + + template + bool WaitFor(std::chrono::duration timeout, Pred pred) { + std::unique_lock lock(m_mutex); + return m_cv.wait_for(lock, timeout, std::move(pred)); + } + + void Notify() { + std::unique_lock lock(m_mutex); + m_cv.notify_all(); + } + +private: + std::mutex m_mutex{}; + std::condition_variable_any m_cv{}; +}; + class AvPlayerSource { public: AvPlayerSource(AvPlayerStateCallback& state, std::string_view path, @@ -139,7 +170,7 @@ private: AvPlayerStateCallback& m_state; SceAvPlayerMemAllocator m_memory_replacement{}; - u64 m_num_output_video_framebuffers{}; + u32 m_num_output_video_framebuffers{}; std::atomic_bool m_is_looping = false; std::atomic_bool m_is_eof = false; @@ -161,7 +192,17 @@ private: std::optional m_video_stream_index{}; std::optional m_audio_stream_index{}; - std::mutex m_state_mutex; + EventCV m_audio_packets_cv{}; + EventCV m_audio_frames_cv{}; + EventCV m_audio_buffers_cv{}; + + EventCV m_video_packets_cv{}; + EventCV m_video_frames_cv{}; + EventCV m_video_buffers_cv{}; + + EventCV m_stop_cv{}; + + std::mutex m_state_mutex{}; std::jthread m_demuxer_thread{}; std::jthread m_video_decoder_thread{}; std::jthread m_audio_decoder_thread{}; diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index 061d1da6..884cd940 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -104,8 +104,9 @@ AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data) } AvPlayerState::~AvPlayerState() { - if (m_up_source && m_current_state == AvState::Play) { - m_up_source->Stop(); + { + std::unique_lock lock(m_source_mutex); + m_up_source.reset(); } if (m_controller_thread.joinable()) { m_controller_thread.request_stop(); @@ -121,18 +122,22 @@ s32 AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType source return -1; } - if (m_up_source != nullptr) { - LOG_ERROR(Lib_AvPlayer, "Only one source is supported."); - return -1; - } + { + std::unique_lock lock(m_source_mutex); + if (m_up_source != nullptr) { + LOG_ERROR(Lib_AvPlayer, "Only one source is supported."); + return -1; + } - m_up_source = std::make_unique(*this, path, m_init_data, source_type); + m_up_source = std::make_unique(*this, path, m_init_data, source_type); + } AddSourceEvent(); return 0; } // Called inside GAME thread s32 AvPlayerState::GetStreamCount() { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { LOG_ERROR(Lib_AvPlayer, "Could not get stream count. No source."); return -1; @@ -142,6 +147,7 @@ s32 AvPlayerState::GetStreamCount() { // Called inside GAME thread s32 AvPlayerState::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info. No source.", stream_index); return -1; @@ -151,6 +157,7 @@ s32 AvPlayerState::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) // Called inside GAME thread s32 AvPlayerState::Start() { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr || m_up_source->Start() < 0) { LOG_ERROR(Lib_AvPlayer, "Could not start playback."); return -1; @@ -199,6 +206,7 @@ void AvPlayerState::StartControllerThread() { // Called inside GAME thread bool AvPlayerState::EnableStream(u32 stream_index) { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { return false; } @@ -207,6 +215,7 @@ bool AvPlayerState::EnableStream(u32 stream_index) { // Called inside GAME thread bool AvPlayerState::Stop() { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr || m_current_state == AvState::Stop) { return false; } @@ -218,6 +227,7 @@ bool AvPlayerState::Stop() { } bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfo& video_info) { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { return false; } @@ -225,6 +235,7 @@ bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfo& video_info) { } bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { return false; } @@ -232,6 +243,7 @@ bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { } bool AvPlayerState::GetAudioData(SceAvPlayerFrameInfo& audio_info) { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { return false; } @@ -239,6 +251,7 @@ bool AvPlayerState::GetAudioData(SceAvPlayerFrameInfo& audio_info) { } bool AvPlayerState::IsActive() { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { return false; } @@ -247,6 +260,7 @@ bool AvPlayerState::IsActive() { } u64 AvPlayerState::CurrentTime() { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { LOG_ERROR(Lib_AvPlayer, "Could not get current time. No source."); return 0; @@ -254,6 +268,16 @@ u64 AvPlayerState::CurrentTime() { return m_up_source->CurrentTime(); } +bool AvPlayerState::SetLooping(bool is_looping) { + std::shared_lock lock(m_source_mutex); + if (m_up_source == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not set loop flag. No source."); + return false; + } + m_up_source->SetLooping(is_looping); + return true; +} + // May be called from different threads void AvPlayerState::OnWarning(u32 id) { // Forward to CONTROLLER thread @@ -313,6 +337,7 @@ bool AvPlayerState::SetState(AvState state) { // Called inside CONTROLLER thread std::optional AvPlayerState::OnBufferingCheckEvent(u32 num_frames) { + std::shared_lock lock(m_source_mutex); if (!m_up_source) { return std::nullopt; } @@ -351,6 +376,7 @@ void AvPlayerState::ProcessEvent() { break; } case AvEventType::AddSource: { + std::shared_lock lock(m_source_mutex); if (m_up_source->FindStreamInfo()) { SetState(AvState::Ready); OnPlaybackStateChanged(AvState::Ready); diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h index eed3265f..ff80b6ce 100644 --- a/src/core/libraries/avplayer/avplayer_state.h +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -12,6 +12,7 @@ #include #include +#include namespace Libraries::AvPlayer { @@ -34,6 +35,7 @@ public: bool GetVideoData(SceAvPlayerFrameInfoEx& video_info); bool IsActive(); u64 CurrentTime(); + bool SetLooping(bool is_looping); private: using ScePthreadMutex = Kernel::ScePthreadMutex; @@ -76,6 +78,7 @@ private: u32 m_thread_affinity; std::atomic_uint32_t m_some_event_result{}; + std::shared_mutex m_source_mutex{}; std::mutex m_state_machine_mutex{}; std::mutex m_event_handler_mutex{}; std::jthread m_controller_thread{}; From 23dddca1f0febc085d0254064a60c5a526575576 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Fri, 16 Aug 2024 10:30:48 +0300 Subject: [PATCH 6/6] last minute fixes --- src/core/libraries/avplayer/avplayer.cpp | 6 ++--- .../libraries/avplayer/avplayer_source.cpp | 25 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index 78e03d78..bd1f6b50 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -172,8 +172,8 @@ bool PS4_SYSV_ABI sceAvPlayerIsActive(SceAvPlayerHandle handle) { return res; } -s32 PS4_SYSV_ABI sceAvPlayerJumpToTime(SceAvPlayerHandle handle, uint64_t jump_time_msec) { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceAvPlayerJumpToTime(SceAvPlayerHandle handle, uint64_t time) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called, time (msec) = {}", time); if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } @@ -226,7 +226,7 @@ s32 PS4_SYSV_ABI sceAvPlayerSetLogCallback(SceAvPlayerLogCallback log_cb, void* } s32 PS4_SYSV_ABI sceAvPlayerSetLooping(SceAvPlayerHandle handle, bool loop_flag) { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called, looping = {}", loop_flag); + LOG_TRACE(Lib_AvPlayer, "called, looping = {}", loop_flag); if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index 964e0fbd..776d389f 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -82,7 +82,7 @@ static s32 CodecTypeToStreamType(AVMediaType codec_type) { } } -static f32 AVRationalToF32(AVRational rational) { +static f32 AVRationalToF32(const AVRational& rational) { return f32(rational.num) / rational.den; } @@ -109,7 +109,8 @@ s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) switch (info.type) { case SCE_AVPLAYER_VIDEO: LOG_INFO(Lib_AvPlayer, "Stream {} is a video stream.", stream_index); - info.details.video.aspect_ratio = AVRationalToF32(p_stream->codecpar->sample_aspect_ratio); + info.details.video.aspect_ratio = + f32(p_stream->codecpar->width) / p_stream->codecpar->height; info.details.video.width = p_stream->codecpar->width; info.details.video.height = p_stream->codecpar->height; if (p_lang_node != nullptr) { @@ -293,7 +294,7 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { return false; } - m_video_frames_cv.Wait([this]() { return m_video_frames.Size() != 0 || m_is_eof; }); + m_video_frames_cv.Wait([this] { return m_video_frames.Size() != 0 || m_is_eof; }); auto frame = m_video_frames.Pop(); if (!frame.has_value()) { @@ -307,7 +308,7 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { duration_cast(high_resolution_clock::now() - m_start_time).count(); if (elapsed_time < frame->info.timestamp) { if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time), - [&]() { return elapsed_time >= frame->info.timestamp; })) { + [&] { return elapsed_time >= frame->info.timestamp; })) { return false; } } @@ -328,7 +329,7 @@ bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { return false; } - m_audio_frames_cv.Wait([this]() { return m_audio_frames.Size() != 0 || m_is_eof; }); + m_audio_frames_cv.Wait([this] { return m_audio_frames.Size() != 0 || m_is_eof; }); auto frame = m_audio_frames.Pop(); if (!frame.has_value()) { @@ -342,7 +343,7 @@ bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { duration_cast(high_resolution_clock::now() - m_start_time).count(); if (elapsed_time < frame->info.timestamp) { if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time), - [&]() { return elapsed_time >= frame->info.timestamp; })) { + [&] { return elapsed_time >= frame->info.timestamp; })) { return false; } } @@ -546,8 +547,8 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { using namespace std::chrono; LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started"); while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) { - if (!m_video_packets_cv.Wait( - stop, [this]() { return m_video_packets.Size() != 0 || m_is_eof; })) { + if (!m_video_packets_cv.Wait(stop, + [this] { return m_video_packets.Size() != 0 || m_is_eof; })) { continue; } const auto packet = m_video_packets.Pop(); @@ -563,7 +564,7 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { return; } while (res >= 0) { - if (!m_video_buffers_cv.Wait(stop, [this]() { return m_video_buffers.Size() != 0; })) { + if (!m_video_buffers_cv.Wait(stop, [this] { return m_video_buffers.Size() != 0; })) { break; } if (m_video_buffers.Size() == 0) { @@ -665,8 +666,8 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { using namespace std::chrono; LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started"); while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) { - if (!m_audio_packets_cv.Wait( - stop, [this]() { return m_audio_packets.Size() != 0 || m_is_eof; })) { + if (!m_audio_packets_cv.Wait(stop, + [this] { return m_audio_packets.Size() != 0 || m_is_eof; })) { continue; } const auto packet = m_audio_packets.Pop(); @@ -681,7 +682,7 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { return; } while (res >= 0) { - if (!m_audio_buffers_cv.Wait(stop, [this]() { return m_audio_buffers.Size() != 0; })) { + if (!m_audio_buffers_cv.Wait(stop, [this] { return m_audio_buffers.Size() != 0; })) { break; } if (m_audio_buffers.Size() == 0) {