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:
parent
e272b92e03
commit
23b65eb553
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue