fix(avplayer): use precise Windows notified waits for EOF polling

Waits on eventCondition go through libkernel's TimedWait::Condition (precise
timed waits on Windows) instead of std::condition_variable::wait_for, as
requested in review.
This commit is contained in:
Roojool 2026-10-08 21:08:42 +05:30 committed by Adrià Franch
parent e272b92e03
commit 23b65eb553
4 changed files with 70 additions and 5 deletions

View File

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

View File

@ -4,7 +4,6 @@
#ifndef CORE_LIBS_PRX_LIBSCEAVPLAYER_INCLUDE_AVPLAYER_HPP
#define CORE_LIBS_PRX_LIBSCEAVPLAYER_INCLUDE_AVPLAYER_HPP
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <memory>
@ -13,6 +12,7 @@
#include <string_view>
#include <thread>
#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<Event> events;
bool quit = false;
std::thread controller;

View File

@ -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();

View File

@ -535,6 +535,8 @@ int TextureCount() {
struct Events {
std::mutex mutex;
std::vector<std::pair<std::int32_t, std::int32_t>> 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<Events*>(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<std::int32_t*>(data) : 0);
}
@ -1089,6 +1097,61 @@ void TestUnsyncedVideoKeepsUpWithAudio() {
Check(sceAvPlayerClose(player) == 0, "close failed");
}
void TestEofStopLatency() {
constexpr int Rounds = 9;
std::array<std::chrono::steady_clock::duration, Rounds> 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<int>(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<std::size_t>(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<std::chrono::microseconds>(median).count() / 1000.0;
std::printf("EofStopLatency median %.3f ms:", medianMs);
for (const auto& sample : elapsed) std::printf(" %.3f", std::chrono::duration_cast<std::chrono::microseconds>(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) {