avplayer WIP

This commit is contained in:
Vladislav Mikhalin 2024-07-26 18:34:36 +03:00
parent e96e66eedd
commit b5c69189e5
24 changed files with 2721 additions and 137 deletions

3
.gitmodules vendored
View File

@ -61,3 +61,6 @@
[submodule "externals/date"] [submodule "externals/date"]
path = externals/date path = externals/date
url = https://github.com/HowardHinnant/date.git url = https://github.com/HowardHinnant/date.git
[submodule "externals/ffmpeg-core"]
path = externals/ffmpeg-core
url = https://github.com/RPCS3/ffmpeg-core.git

View File

@ -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.cpp
src/core/libraries/disc_map/disc_map.h src/core/libraries/disc_map/disc_map.h
src/core/libraries/disc_map/disc_map_codes.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.cpp
src/core/libraries/avplayer/avplayer.h src/core/libraries/avplayer/avplayer.h
) )
@ -588,7 +598,7 @@ endif()
create_target_directory_groups(shadps4) create_target_directory_groups(shadps4)
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API) 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) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3)
if (APPLE) if (APPLE)

View File

@ -47,6 +47,11 @@ else()
endif() endif()
endif() endif()
if (NOT TARGET ffmpeg)
set(ARCHITECTURE "x86_64")
add_subdirectory(ffmpeg-core)
endif()
# Zlib-Ng # Zlib-Ng
if (NOT TARGET zlib-ng::zlib) if (NOT TARGET zlib-ng::zlib)
set(ZLIB_ENABLE_TESTS OFF) set(ZLIB_ENABLE_TESTS OFF)

1
externals/ffmpeg-core vendored Submodule

@ -0,0 +1 @@
Subproject commit e30b7d7fe228bfb3f6e41ce1040b44a15eb7d5e0

View File

@ -106,12 +106,12 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, DiscMap) \ SUB(Lib, DiscMap) \
SUB(Lib, Png) \ SUB(Lib, Png) \
SUB(Lib, PlayGo) \ SUB(Lib, PlayGo) \
SUB(Lib, Random) \
SUB(Lib, Usbd) \ SUB(Lib, Usbd) \
SUB(Lib, Ajm) \ SUB(Lib, Ajm) \
SUB(Lib, ErrorDialog) \ SUB(Lib, ErrorDialog) \
SUB(Lib, ImeDialog) \ SUB(Lib, ImeDialog) \
SUB(Lib, AvPlayer) \ SUB(Lib, AvPlayer) \
SUB(Lib, Random) \
CLS(Frontend) \ CLS(Frontend) \
CLS(Render) \ CLS(Render) \
SUB(Render, Vulkan) \ SUB(Render, Vulkan) \

View File

@ -25,9 +25,9 @@ void MntPoints::UnmountAll() {
m_mnt_pairs.clear(); 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 // 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("//"); size_t pos = corrected_path.find("//");
while (pos != std::string::npos) { while (pos != std::string::npos) {
corrected_path.replace(pos, 2, "/"); corrected_path.replace(pos, 2, "/");

View File

@ -31,7 +31,7 @@ public:
void Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder); void Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder);
void UnmountAll(); 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 MntPair* GetMount(const std::string& guest_path) {
const auto it = std::ranges::find_if( const auto it = std::ranges::find_if(

View File

@ -175,7 +175,6 @@ int PS4_SYSV_ABI sceAudioOutGetLastOutputTime() {
} }
int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) { int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) {
int type = 0; int type = 0;
int channels_num = 0; int channels_num = 0;
@ -235,11 +234,11 @@ int PS4_SYSV_ABI sceAudioOutGetSystemState() {
} }
int PS4_SYSV_ABI sceAudioOutInit() { int PS4_SYSV_ABI sceAudioOutInit() {
LOG_INFO(Lib_AudioOut, "called");
if (audio != nullptr) { if (audio != nullptr) {
return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT; return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT;
} }
audio = std::make_unique<Audio::SDLAudio>(); audio = std::make_unique<Audio::SDLAudio>();
LOG_INFO(Lib_AudioOut, "called");
return ORBIS_OK; return ORBIS_OK;
} }
@ -324,10 +323,12 @@ int PS4_SYSV_ABI sceAudioOutOpenEx() {
} }
s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) { s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) {
LOG_TRACE(Lib_AudioOut, "called");
return audio->AudioOutOutput(handle, ptr); return audio->AudioOutOutput(handle, ptr);
} }
int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) { int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) {
LOG_TRACE(Lib_AudioOut, "called");
for (u32 i = 0; i < num; i++) { for (u32 i = 0; i < num; i++) {
if (auto err = audio->AudioOutOutput(param[i].handle, param[i].ptr); err != 0) if (auto err = audio->AudioOutOutput(param[i].handle, param[i].ptr); err != 0)
return err; return err;

View File

@ -1,21 +1,36 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
// Generated By moduleGenerator
#include "avplayer.h" #include "avplayer.h"
#include "avplayer_impl.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/libraries/error_codes.h" #include "core/libraries/error_codes.h"
#include "core/libraries/kernel/thread_management.h"
#include "core/libraries/libs.h" #include "core/libraries/libs.h"
#include <mutex>
namespace Libraries::AvPlayer { namespace Libraries::AvPlayer {
int PS4_SYSV_ABI sceAvPlayerAddSource() { using namespace Kernel;
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
return ORBIS_OK; 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"); LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return ORBIS_OK; return ORBIS_OK;
} }
@ -24,122 +39,307 @@ int PS4_SYSV_ABI sceAvPlayerChangeStream() {
return ORBIS_OK; 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"); LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
return ORBIS_OK; 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"); LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceAvPlayerDisableStream() { s32 PS4_SYSV_ABI sceAvPlayerSetLooping(SceAvPlayerHandle handle, bool loop_flag) {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceAvPlayerEnableStream() { s32 PS4_SYSV_ABI sceAvPlayerSetTrickSpeed(SceAvPlayerHandle handle, s32 trick_speed) {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
if (handle == nullptr) {
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
}
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceAvPlayerGetAudioData() { s32 PS4_SYSV_ABI sceAvPlayerStart(SceAvPlayerHandle handle) {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); LOG_TRACE(Lib_AvPlayer, "called");
return ORBIS_OK; 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"); 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() { s32 PS4_SYSV_ABI sceAvPlayerStreamCount(SceAvPlayerHandle handle) {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); LOG_TRACE(Lib_AvPlayer, "called");
return ORBIS_OK; 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() { s32 PS4_SYSV_ABI sceAvPlayerVprintf(const char* format, va_list args) {
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() {
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
return ORBIS_OK; return ORBIS_OK;
} }

View File

@ -11,33 +11,279 @@ class SymbolsResolver;
namespace Libraries::AvPlayer { namespace Libraries::AvPlayer {
int PS4_SYSV_ABI sceAvPlayerAddSource(); class AvPlayer;
int PS4_SYSV_ABI sceAvPlayerAddSourceEx();
int PS4_SYSV_ABI sceAvPlayerChangeStream(); using SceAvPlayerHandle = AvPlayer*;
int PS4_SYSV_ABI sceAvPlayerClose();
int PS4_SYSV_ABI sceAvPlayerCurrentTime(); enum SceAvPlayerUriType { SCE_AVPLAYER_URI_TYPE_SOURCE = 0 };
int PS4_SYSV_ABI sceAvPlayerDisableStream();
int PS4_SYSV_ABI sceAvPlayerEnableStream(); struct SceAvPlayerUri {
int PS4_SYSV_ABI sceAvPlayerGetAudioData(); const char* name;
int PS4_SYSV_ABI sceAvPlayerGetStreamInfo(); u32 length;
int PS4_SYSV_ABI sceAvPlayerGetVideoData(); };
int PS4_SYSV_ABI sceAvPlayerGetVideoDataEx();
int PS4_SYSV_ABI sceAvPlayerInit(); enum SceAvPlayerSourceType {
int PS4_SYSV_ABI sceAvPlayerInitEx(); SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN = 0,
int PS4_SYSV_ABI sceAvPlayerIsActive(); SCE_AVPLAYER_SOURCE_TYPE_FILE_MP4 = 1,
int PS4_SYSV_ABI sceAvPlayerJumpToTime(); SCE_AVPLAYER_SOURCE_TYPE_HLS = 8
int PS4_SYSV_ABI sceAvPlayerPause(); };
int PS4_SYSV_ABI sceAvPlayerPostInit();
int PS4_SYSV_ABI sceAvPlayerPrintf(); struct SceAvPlayerSourceDetails {
int PS4_SYSV_ABI sceAvPlayerResume(); SceAvPlayerUri uri;
int PS4_SYSV_ABI sceAvPlayerSetAvSyncMode(); u8 reserved1[64];
int PS4_SYSV_ABI sceAvPlayerSetLogCallback(); SceAvPlayerSourceType source_type;
int PS4_SYSV_ABI sceAvPlayerSetLooping(); u8 reserved2[44];
int PS4_SYSV_ABI sceAvPlayerSetTrickSpeed(); };
int PS4_SYSV_ABI sceAvPlayerStart();
int PS4_SYSV_ABI sceAvPlayerStop(); struct SceAvPlayerAudio {
int PS4_SYSV_ABI sceAvPlayerStreamCount(); u16 channel_count;
int PS4_SYSV_ABI sceAvPlayerVprintf(); 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); void RegisterlibSceAvPlayer(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::AvPlayer } // namespace Libraries::AvPlayer

View File

@ -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 <algorithm> // std::equal
#include <cctype> // std::tolower
#include <string_view> // 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<int>(params.priority)};
if (scePthreadAttrSetschedparam(&attr, &param) != 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<unsigned char>(a)) ==
std::tolower(static_cast<unsigned char>(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

View File

@ -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 <optional>
#include <utility>
#include <queue>
#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 T>
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<T>(value));
}
std::optional<T> 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<T> 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

View File

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

View File

@ -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 <magic_enum.hpp>
extern "C" {
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
}
#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<u8*>(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<AvPlayerFileStreamer*>(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<AvPlayerFileStreamer*>(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

View File

@ -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 <string_view>
#include <vector>
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

View File

@ -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<AvPlayer*>(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<AvPlayer*>(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<AvPlayer*>(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<AvPlayer*>(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<AvPlayer*>(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<AvPlayer*>(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<AvPlayer*>(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<AvPlayer*>(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<AvPlayerState>(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

View File

@ -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 <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
#include <memory>
#include <vector>
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<AvPlayerState> 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

View File

@ -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 <magic_enum.hpp>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
}
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<AvPlayerFileStreamer>(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<Core::FileSys::MntPoints>::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<u8*>(
m_memory_replacement.allocate(m_memory_replacement.object_ptr, frame->width, size));
m_video_frame_storage = std::span<u8>(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<u8*>(
m_memory_replacement.allocate(m_memory_replacement.object_ptr, 0x40, size));
m_audio_frame_storage = std::span<u8>(ptr, size);
}
return m_audio_frame_storage.data();
}
void AvPlayerSource::SetLooping(bool is_looping) {
m_is_looping = is_looping;
}
std::optional<bool> 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<u8*>(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<milliseconds>(high_resolution_clock::now() - m_start_time).count();
while (elapsed_time < timestamp) {
sceKernelUsleep((timestamp - elapsed_time) * 1000);
elapsed_time =
duration_cast<milliseconds>(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<milliseconds>(high_resolution_clock::now() - m_start_time).count();
while (elapsed_time < timestamp) {
sceKernelUsleep((timestamp - elapsed_time) * 1000);
elapsed_time =
duration_cast<milliseconds>(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<milliseconds>(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<AvPlayerSource*>(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<AvPlayerSource*>(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<AvPlayerSource*>(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

View File

@ -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 <atomic>
#include <chrono>
#include <optional>
#include <string>
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<u8*>(
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<u8> 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<bool> 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<AVPacket, decltype(&ReleaseAVPacket)>;
using AVFramePtr = std::unique_ptr<AVFrame, decltype(&ReleaseAVFrame)>;
using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(&ReleaseAVCodecContext)>;
using SWRContextPtr = std::unique_ptr<SwrContext, decltype(&ReleaseSWRContext)>;
using SWSContextPtr = std::unique_ptr<SwsContext, decltype(&ReleaseSWSContext)>;
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<IDataStreamer> m_up_data_streamer;
AVFormatContext* m_avformat_context{};
AvPlayerQueue<AVPacketPtr> m_audio_packets;
AvPlayerQueue<AVPacketPtr> m_video_packets;
AvPlayerQueue<AVFramePtr> m_audio_frames;
AvPlayerQueue<AVFramePtr> m_video_frames;
std::span<u8> m_video_frame_storage;
std::span<u8> m_audio_frame_storage;
AVCodecContextPtr m_video_codec_context{nullptr, &ReleaseAVCodecContext};
AVCodecContextPtr m_audio_codec_context{nullptr, &ReleaseAVCodecContext};
std::optional<int> m_video_stream_index{};
std::optional<int> 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

View File

@ -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 <magic_enum.hpp>
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<AvPlayerState*>(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<char*>(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<char*>(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<char*>(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<char*>(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<AvPlayerSource>(*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<AvPlayerState*>(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<bool> 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

View File

@ -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 <memory>
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<bool> 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<AvPlayerSource> 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<AvState> m_current_state;
std::atomic<AvState> 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<AvPlayerEvent> m_event_queue{};
};
} // namespace Libraries::AvPlayer

View File

@ -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_ALREADY_EXISTS = 0x80551613;
constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551622; 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 // AppContent library
constexpr int ORBIS_APP_CONTENT_ERROR_PARAMETER = 0x80D90002; constexpr int ORBIS_APP_CONTENT_ERROR_PARAMETER = 0x80D90002;

View File

@ -158,19 +158,24 @@ void init_pthreads();
void pthreadInitSelfMainThread(); void pthreadInitSelfMainThread();
int PS4_SYSV_ABI scePthreadAttrInit(ScePthreadAttr* attr); 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 scePthreadAttrSetdetachstate(ScePthreadAttr* attr, int detachstate);
int PS4_SYSV_ABI scePthreadAttrSetinheritsched(ScePthreadAttr* attr, int inheritSched); int PS4_SYSV_ABI scePthreadAttrSetinheritsched(ScePthreadAttr* attr, int inheritSched);
int PS4_SYSV_ABI scePthreadAttrSetschedparam(ScePthreadAttr* attr, int PS4_SYSV_ABI scePthreadAttrSetschedparam(ScePthreadAttr* attr,
const SceKernelSchedParam* param); const SceKernelSchedParam* param);
int PS4_SYSV_ABI scePthreadAttrSetschedpolicy(ScePthreadAttr* attr, int policy); int PS4_SYSV_ABI scePthreadAttrSetschedpolicy(ScePthreadAttr* attr, int policy);
ScePthread PS4_SYSV_ABI scePthreadSelf();
int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr, int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr,
const /*SceKernelCpumask*/ u64 mask); 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 scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpumask*/ u64 mask);
int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u64* mask); int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u64* mask);
int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr, int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr,
PthreadEntryFunc start_routine, void* arg, const char* name); 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); 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, int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr,
const char* name); 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 scePthreadMutexattrInit(ScePthreadMutexattr* attr);
int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type); int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type);
int PS4_SYSV_ABI scePthreadMutexattrSetprotocol(ScePthreadMutexattr* attr, int protocol); 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 * Cond calls
*/ */

View File

@ -50,6 +50,7 @@ u64 PS4_SYSV_ABI sceKernelGetProcessTime();
u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter();
u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency();
u64 PS4_SYSV_ABI sceKernelReadTsc(); u64 PS4_SYSV_ABI sceKernelReadTsc();
int PS4_SYSV_ABI sceKernelUsleep(u32 microseconds);
void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym); void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym);