// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include "prx/libSceAvPlayer/include/AvPlayer.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestHeap.hpp" namespace AvPlayer { namespace { bool EqualsIgnoreCase(std::string_view left, std::string_view right) { return std::ranges::equal(left, right, [](unsigned char a, unsigned char b) { return std::tolower(a) == std::tolower(b); }); } bool SameLanguage(const char* preferred, const char* code) { return preferred[0] != '\0' && std::strncmp(preferred, code, 4) == 0; } void* APS5_VABI GuestAllocate_nid_no_patch(void*, std::uint32_t alignment, std::uint32_t size) { return GuestHeap::GuestHeapAlign_nid_postfix(std::max(alignment, 16), size); } void APS5_VABI GuestDeallocate_nid_no_patch(void*, void* memory) { GuestHeap::GuestHeapFree_nid_postfix(memory); } AvPlayerMemAllocator WithGuestHeapFallback(AvPlayerMemAllocator memory) { if (!memory.allocate || !memory.deallocate) { memory.allocate = GuestAllocate_nid_no_patch; memory.deallocate = GuestDeallocate_nid_no_patch; } if (!memory.allocate_texture || !memory.deallocate_texture) { memory.allocate_texture = GuestAllocate_nid_no_patch; memory.deallocate_texture = GuestDeallocate_nid_no_patch; } return memory; } } std::uint32_t DetectSourceType(std::string_view path) { std::string_view name = path; if (name.find("://") != std::string_view::npos) name = name.substr(0, name.find_first_of("?#")); const auto dot = name.rfind('.'); if (dot == std::string_view::npos) return SourceTypeUnknown; auto extension = name.substr(dot); extension = extension.substr(0, extension.find('/')); for (const auto mp4 : {".mp4", ".m4v", ".m3d", ".m4a", ".mov"}) { if (EqualsIgnoreCase(extension, mp4)) return SourceTypeFileMp4; } if (EqualsIgnoreCase(extension, ".m3u8")) return SourceTypeHls; return SourceTypeUnknown; } Player::Player(const AvPlayerInitData& init, std::uint32_t bufferCount) : memory(WithGuestHeapFallback(init.memory_replacement)), callback(init.event_replacement), videoBuffers(bufferCount) { if (init.file_replacement.open && init.file_replacement.close && init.file_replacement.read_offset && init.file_replacement.size) { file = init.file_replacement; } autoStartEnabled = init.auto_start || callback.event_callback == nullptr; if (init.default_language) { for (std::size_t index = 0; index < 3 && init.default_language[index] != '\0'; ++index) language[index] = init.default_language[index]; } controller = std::thread([this] { controllerLoop(); }); } Player::~Player() { { std::lock_guard lock(eventMutex); quit = true; } eventCondition.notify_all(); controller.join(); std::lock_guard lock(mutex); source.reset(); } void Player::queue(const Event& event) { { std::lock_guard lock(eventMutex); events.push_back(event); } eventCondition.notify_all(); } void Player::OnWarning(std::int32_t code) { queue({EventWarningId, code, false, false}); } void Player::OnError() { queue({0, 0, false, true}); } void Player::controllerLoop() { for (;;) { Event event{}; bool pending = false; { std::unique_lock lock(eventMutex); eventCondition.wait_for(lock, std::chrono::milliseconds(5), [this] { return quit || !events.empty(); }); if (quit) return; if (!events.empty()) { event = events.front(); events.pop_front(); pending = true; } } if (pending) { deliver(event); } else { checkEndOfFile(); } } } void Player::deliver(const Event& event) { if (event.error) { std::lock_guard lock(mutex); if (source && state != State::Stop) { previous = state; state = State::Error; } return; } if (event.autoStart) { autoStart(); return; } if (!callback.event_callback) return; std::int32_t warning = event.warning; callback.event_callback(callback.object_ptr, event.id, 0, event.id == EventWarningId ? &warning : nullptr); } void Player::autoStart() { const int count = StreamCount(); if (count <= 0) { Stop(); return; } int video = -1; int audio = -1; for (int index = 0; index < count; ++index) { AvPlayerStreamInfo info{}; if (GetStreamInfo(static_cast(index), info) != SCE_OK) { Stop(); return; } if (info.type == StreamTypeVideo && (video == -1 || SameLanguage(language, info.details.video.language_code))) video = index; if (info.type == StreamTypeAudio && (audio == -1 || SameLanguage(language, info.details.audio.language_code))) audio = index; } if (video != -1) EnableStream(static_cast(video)); if (audio != -1) EnableStream(static_cast(audio)); Start(); } void Player::checkEndOfFile() { { std::lock_guard lock(mutex); if (!source || state != State::Play || !source->Finished()) return; previous = state; state = State::EndOfFile; } deliver({EventStateStop, 0, false, false}); } int Player::PostInit(const AvPlayerPostInitData& data) { std::lock_guard lock(mutex); demuxVideoBytes = data.demux_video_buffer_size; if (source) source->SetDemuxVideoBufferSize(demuxVideoBytes); return SCE_OK; } int Player::AddSource(std::string_view path, std::uint32_t sourceType) { if (path.empty()) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; if (sourceType == SourceTypeUnknown) sourceType = DetectSourceType(path); if (sourceType == SourceTypeHls) NotImplemented_nid_no_patch("sceAvPlayerAddSource (HLS)"); std::lock_guard lock(mutex); if (source) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; auto opened = OpenSource({memory, file, videoBuffers}, std::string(path), *this); if (!opened) { previous = state; state = State::Error; return SCE_AVPLAYER_ERROR_OPERATION_FAILED; } if (opened->StreamCount() == 0) { source = std::move(opened); previous = state; state = State::Error; queue({EventWarningId, SCE_AVPLAYER_ERROR_NOT_SUPPORTED, false, false}); return SCE_OK; } opened->SetLooping(looping); opened->SetSpeed(speed); opened->SetSyncMode(syncMode); opened->SetDemuxVideoBufferSize(demuxVideoBytes); source = std::move(opened); previous = state; state = State::Ready; queue(autoStartEnabled ? Event{0, 0, true, false} : Event{EventStateReady, 0, false, false}); return SCE_OK; } int Player::StreamCount() { std::lock_guard lock(mutex); if (!source) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; return static_cast(source->StreamCount()); } int Player::GetStreamInfo(std::uint32_t index, AvPlayerStreamInfo& info) { std::lock_guard lock(mutex); if (!source || !source->GetStreamInfo(index, info)) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; return SCE_OK; } int Player::GetStreamInfoEx(std::uint32_t index, AvPlayerStreamInfoEx& info) { std::lock_guard lock(mutex); AvPlayerStreamInfoEx described{}; if (!source || !source->GetStreamInfoEx(index, described)) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; described.this_size = info.this_size; info = described; return SCE_OK; } int Player::EnableStream(std::uint32_t index) { std::lock_guard lock(mutex); if (!source || !source->EnableStream(index)) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; return SCE_OK; } int Player::DisableStream(std::uint32_t index) { std::lock_guard lock(mutex); if (!source || !source->DisableStream(index)) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; return SCE_OK; } int Player::ChangeStream(std::uint32_t from, std::uint32_t to) { std::lock_guard lock(mutex); if (!source || state == State::Error || !source->ChangeStream(from, to)) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; return SCE_OK; } int Player::Start() { std::lock_guard lock(mutex); if (!source) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; if (state != State::Ready && state != State::Stop && stopLocked() != SCE_OK) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; const int result = source->Start(); if (result != SCE_OK) { previous = state; state = State::Error; return result; } previous = state; state = State::Play; queue({EventStatePlay, 0, false, false}); return SCE_OK; } int Player::stopLocked() { if (!source || state == State::Initial || state == State::Ready || state == State::Stop) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; source->Stop(); previous = state; state = State::Stop; queue({EventStateStop, 0, false, false}); return SCE_OK; } int Player::Stop() { std::lock_guard lock(mutex); return stopLocked(); } int Player::Pause() { std::lock_guard lock(mutex); if (state == State::EndOfFile) return SCE_OK; if (!source || state != State::Play) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; source->Pause(); previous = state; state = State::Pause; queue({EventStatePause, 0, false, false}); return SCE_OK; } int Player::Resume() { std::lock_guard lock(mutex); if (!source || state != State::Pause) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; source->Resume(); std::swap(state, previous); queue({EventStatePlay, 0, false, false}); return SCE_OK; } int Player::JumpToTime(std::uint64_t milliseconds) { std::lock_guard lock(mutex); if (!source || (state != State::Ready && state != State::Play && state != State::Pause)) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; source->Jump(milliseconds); return SCE_OK; } int Player::SetLooping(bool enabled) { std::lock_guard lock(mutex); looping = enabled; if (source) source->SetLooping(enabled); return SCE_OK; } int Player::SetTrickSpeed(std::int32_t trickSpeed) { if (trickSpeed == 0) return SCE_AVPLAYER_ERROR_INVALID_PARAMS; std::lock_guard lock(mutex); if (state == State::Stop || state == State::EndOfFile || state == State::Error) return SCE_AVPLAYER_ERROR_OPERATION_FAILED; speed = trickSpeed; if (source) source->SetSpeed(trickSpeed); return SCE_OK; } int Player::SetAvSyncMode(std::uint32_t mode) { if (mode != SyncModeDefault && mode != SyncModeNone) return SCE_AVPLAYER_ERROR_INVALID_PARAMS; std::lock_guard lock(mutex); syncMode = mode; if (source) source->SetSyncMode(mode); return SCE_OK; } bool Player::GetVideoData(AvPlayerFrameInfoEx& info) { std::lock_guard lock(mutex); if (!source || state != State::Play) return false; return source->GetVideoData(info); } bool Player::GetVideoData(AvPlayerFrameInfo& info) { AvPlayerFrameInfoEx extended{}; if (!GetVideoData(extended)) return false; info = {}; info.p_data = static_cast(extended.p_data); info.timestamp = extended.timestamp; info.details.video.width = extended.details.video.width; info.details.video.height = extended.details.video.height; info.details.video.aspect_ratio = extended.details.video.aspect_ratio; std::memcpy(info.details.video.language_code, extended.details.video.language_code, sizeof(info.details.video.language_code)); return true; } bool Player::GetAudioData(AvPlayerFrameInfo& info) { std::lock_guard lock(mutex); if (!source || state != State::Play) return false; return source->GetAudioData(info); } bool Player::IsActive() { std::lock_guard lock(mutex); if (!source || state == State::Initial || state == State::Stop || state == State::EndOfFile || state == State::Error) return false; return !source->Finished(); } std::uint64_t Player::CurrentTime() { std::lock_guard lock(mutex); return source ? source->CurrentTime() : 0; } }