diff --git a/core/libs/prx/libSceAvPlayer/AvPlayerLibrary.cmake b/core/libs/prx/libSceAvPlayer/AvPlayerLibrary.cmake index 86a5d13d3..32fc8bb78 100644 --- a/core/libs/prx/libSceAvPlayer/AvPlayerLibrary.cmake +++ b/core/libs/prx/libSceAvPlayer/AvPlayerLibrary.cmake @@ -4,6 +4,7 @@ function(add_sce_avplayer_library target) ${avPlayerDir}/Export.cpp ${avPlayerDir}/src/Player.cpp ${avPlayerDir}/src/Source.cpp + ${avPlayerDir}/../libkernel/Time/TimedWait.cpp ) target_include_directories(${target} PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(${target} PRIVATE ffmpeg libc) diff --git a/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp b/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp index c401749ba..740f552d2 100644 --- a/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp +++ b/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp @@ -4,7 +4,6 @@ #ifndef CORE_LIBS_PRX_LIBSCEAVPLAYER_INCLUDE_AVPLAYER_HPP #define CORE_LIBS_PRX_LIBSCEAVPLAYER_INCLUDE_AVPLAYER_HPP -#include #include #include #include @@ -13,6 +12,7 @@ #include #include #include "SceTypes.hpp" +#include "prx/libkernel/Time/include/TimedWait.hpp" namespace AvPlayer { @@ -151,7 +151,7 @@ private: std::uint32_t demuxVideoBytes = 0; std::mutex eventMutex; - std::condition_variable eventCondition; + TimedWait::Condition eventCondition; std::deque events; bool quit = false; std::thread controller; diff --git a/core/libs/prx/libSceAvPlayer/src/Player.cpp b/core/libs/prx/libSceAvPlayer/src/Player.cpp index ab1aebb40..6f418b842 100644 --- a/core/libs/prx/libSceAvPlayer/src/Player.cpp +++ b/core/libs/prx/libSceAvPlayer/src/Player.cpp @@ -78,7 +78,7 @@ Player::~Player() { std::lock_guard lock(eventMutex); quit = true; } - eventCondition.notify_all(); + eventCondition.NotifyAll(); controller.join(); std::lock_guard lock(mutex); source.reset(); @@ -89,7 +89,7 @@ void Player::queue(const Event& event) { std::lock_guard lock(eventMutex); events.push_back(event); } - eventCondition.notify_all(); + eventCondition.NotifyAll(); } void Player::OnWarning(std::int32_t code) { @@ -106,7 +106,7 @@ void Player::controllerLoop() { bool pending = false; { std::unique_lock lock(eventMutex); - eventCondition.wait_for(lock, std::chrono::milliseconds(5), [this] { return quit || !events.empty(); }); + eventCondition.WaitUntil(lock, TimedWait::DeadlineNanos(5000), [this] { return quit || !events.empty(); }); if (quit) return; if (!events.empty()) { event = events.front(); diff --git a/core/libs/tests/GuestAvPlayer.cpp b/core/libs/tests/GuestAvPlayer.cpp index 77adb378e..dad55fead 100644 --- a/core/libs/tests/GuestAvPlayer.cpp +++ b/core/libs/tests/GuestAvPlayer.cpp @@ -535,6 +535,8 @@ int TextureCount() { struct Events { std::mutex mutex; std::vector> received; + std::chrono::steady_clock::time_point stopAt{}; + bool hasStop = false; bool Seen(std::int32_t id, std::int32_t warning = 0) { std::lock_guard lock(mutex); @@ -544,6 +546,8 @@ struct Events { void Clear() { std::lock_guard lock(mutex); received.clear(); + stopAt = {}; + hasStop = false; } }; @@ -551,6 +555,10 @@ void APS5_VABI OnEvent(void* object, std::int32_t id, std::int32_t source, void* auto* events = static_cast(object); if (source != 0 || (id == EventWarning) != (data != nullptr)) std::abort(); std::lock_guard lock(events->mutex); + if (id == EventStop && !events->hasStop) { + events->stopAt = std::chrono::steady_clock::now(); + events->hasStop = true; + } events->received.emplace_back(id, id == EventWarning ? *static_cast(data) : 0); } @@ -1089,6 +1097,61 @@ void TestUnsyncedVideoKeepsUpWithAudio() { Check(sceAvPlayerClose(player) == 0, "close failed"); } +void TestEofStopLatency() { + constexpr int Rounds = 9; + std::array elapsed{}; + for (int round = 0; round < Rounds; ++round) { + Events events; + AvPlayerInitData init = InitData(&events); + auto* player = sceAvPlayerInit(&init); + Check(player != nullptr, "eof latency init failed"); + Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4") == 0, "eof latency add source failed"); + Check(WaitFor([&] { return events.Seen(EventReady); }), "eof latency ready event missing"); + Check(sceAvPlayerEnableStream(player, VideoStream) == 0, "eof latency enable video failed"); + Check(sceAvPlayerEnableStream(player, EnglishAudioStream) == 0, "eof latency enable audio failed"); + Check(sceAvPlayerStart(player) == 0, "eof latency start failed"); + Check(WaitFor([&] { return events.Seen(EventPlay); }), "eof latency play event missing"); + AvPlayerFrameInfoEx frame{}; + AvPlayerFrameInfo sound{}; + const auto drainDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + bool ended = false; + while (std::chrono::steady_clock::now() < drainDeadline) { + while (sceAvPlayerGetAudioData(player, &sound)) CheckEnglishAudio(sound); + while (sceAvPlayerGetVideoDataEx(player, &frame)) CheckVideoFrame(frame); + if (!sceAvPlayerIsActive(player)) { + ended = true; + break; + } + std::this_thread::yield(); + } + Check(ended, "eof latency playback never ended"); + const auto inactive = std::chrono::steady_clock::now(); + Check(WaitFor([&] { return events.Seen(EventStop); }), "eof latency stop event missing"); + std::chrono::steady_clock::time_point stopAt{}; + { + std::lock_guard lock(events.mutex); + Check(events.hasStop, "eof latency stop timestamp missing"); + stopAt = events.stopAt; + const int stops = static_cast(std::count_if(events.received.begin(), events.received.end(), [](const auto& event) { return event.first == EventStop; })); + Check(stops == 1, "eof latency stop delivered " + std::to_string(stops) + " times"); + } + elapsed[static_cast(round)] = stopAt > inactive ? stopAt - inactive : std::chrono::steady_clock::duration::zero(); + Check(sceAvPlayerCurrentTime(player) >= 950, "eof latency clock not at the end"); + Check(!sceAvPlayerIsActive(player), "eof latency active after the end"); + Check(sceAvPlayerClose(player) == 0, "eof latency close failed"); + std::lock_guard lock(allocations.mutex); + Check(allocations.blocks.empty(), "eof latency allocations leaked"); + } + std::sort(elapsed.begin(), elapsed.end()); + const auto median = elapsed[elapsed.size() / 2]; + const auto medianMs = std::chrono::duration_cast(median).count() / 1000.0; + std::printf("EofStopLatency median %.3f ms:", medianMs); + for (const auto& sample : elapsed) std::printf(" %.3f", std::chrono::duration_cast(sample).count() / 1000.0); + std::printf("\n"); + std::fflush(stdout); + Check(median < std::chrono::milliseconds(10), "eof stop median " + std::to_string(medianMs) + " ms exceeds 10 ms"); +} + } int main() { @@ -1104,6 +1167,7 @@ int main() { TestPs5ExtendedInitLayout(); TestAutoStartKeepsStartedPlayback(); TestUnsyncedVideoKeepsUpWithAudio(); + TestEofStopLatency(); std::puts("AvPlayer tests passed"); return 0; } catch (const std::exception& error) {