diff --git a/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp b/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp index 0eeb944fb..c401749ba 100644 --- a/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp +++ b/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp @@ -130,6 +130,8 @@ private: void deliver(const Event& event); void autoStart(); void checkEndOfFile(); + bool readyLocked() const; + int startLocked(); int stopLocked(); AvPlayerFileReplacement file{}; diff --git a/core/libs/prx/libSceAvPlayer/src/Player.cpp b/core/libs/prx/libSceAvPlayer/src/Player.cpp index 1e265d395..bdf69bfc3 100644 --- a/core/libs/prx/libSceAvPlayer/src/Player.cpp +++ b/core/libs/prx/libSceAvPlayer/src/Player.cpp @@ -136,7 +136,15 @@ void Player::deliver(const Event& event) { callback.event_callback(callback.object_ptr, event.id, 0, event.id == EventWarningId ? &warning : nullptr); } +bool Player::readyLocked() const { + return source && state == State::Ready; +} + void Player::autoStart() { + { + std::lock_guard lock(mutex); + if (!readyLocked()) return; + } const int count = StreamCount(); if (count <= 0) { Stop(); @@ -155,7 +163,8 @@ void Player::autoStart() { } if (video != -1) EnableStream(static_cast(video)); if (audio != -1) EnableStream(static_cast(audio)); - Start(); + std::lock_guard lock(mutex); + if (readyLocked()) startLocked(); } void Player::checkEndOfFile() { @@ -246,6 +255,10 @@ int Player::ChangeStream(std::uint32_t from, std::uint32_t to) { int Player::Start() { std::lock_guard lock(mutex); + return startLocked(); +} + +int Player::startLocked() { 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(); diff --git a/core/libs/tests/GuestAvPlayer.cpp b/core/libs/tests/GuestAvPlayer.cpp index 2fb32a5db..1d50a1497 100644 --- a/core/libs/tests/GuestAvPlayer.cpp +++ b/core/libs/tests/GuestAvPlayer.cpp @@ -1034,6 +1034,32 @@ void TestPs5ExtendedInitLayout() { run(true); } +void TestAutoStartKeepsStartedPlayback() { + bool startedByTitle = false; + for (int attempt = 0; attempt < 10 && !startedByTitle; ++attempt) { + AvPlayerInitData init = InitData(nullptr); + auto* player = sceAvPlayerInit(&init); + Check(player != nullptr, "init failed"); + Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4") == 0, "add source failed"); + startedByTitle = sceAvPlayerEnableStream(player, VideoStream) == 0 && sceAvPlayerEnableStream(player, EnglishAudioStream) == 0; + if (startedByTitle) { + const auto releasedTextures = [] { + std::lock_guard lock(allocations.mutex); + return allocations.textures - ActiveTextureCountLocked(); + }; + Check(sceAvPlayerStart(player) == 0, "start failed"); + const int released = releasedTextures(); + AvPlayerFrameInfoEx frame{}; + Check(WaitFor([&] { return sceAvPlayerGetVideoDataEx(player, &frame) != 0; }), "no video after the title's start"); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + Check(releasedTextures() == released, "auto-start restarted playback the title had started"); + Check(IsAllocation(frame.p_data, true), "auto-start released the frame the title holds"); + } + Check(sceAvPlayerClose(player) == 0, "close failed"); + } + Check(startedByTitle, "auto-start won every race against the title's own start"); +} + void TestUnsyncedVideoKeepsUpWithAudio() { AvPlayerInitData init = InitData(nullptr); auto* player = sceAvPlayerInit(&init); @@ -1075,6 +1101,7 @@ int main() { TestFileReplacementAutoStart(); TestHandedOutFramesStayIntact(); TestPs5ExtendedInitLayout(); + TestAutoStartKeepsStartedPlayback(); TestUnsyncedVideoKeepsUpWithAudio(); std::puts("AvPlayer tests passed"); return 0; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 4656d0fbb..559981ba2 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -198,6 +198,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAvPlayerStartEx](../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - start info layout unknown; it is ignored and playback starts as with `sceAvPlayerStart` - [sceAvPlayerInit / sceAvPlayerInitEx](../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - behaviour without a memory replacement unknown; frame and sample buffers then come from the guest heap - [sceAvPlayerInitEx](../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - the PS5 init data layout (`this_size` 0x230: auto start at 0x74, nine 0x30-byte thread parameter blocks from 0x78, output video frame buffer count at 0x228) follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/avPlayer.cpp), [prosper](https://github.com/mattias800/prosper/blob/main/prosper/src/hle/video/avplayer.cpp), [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Media/AvPlayer.cs) and [sharpemu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/AvPlayer/AvPlayerExports.cs), which agree on it. PPSA25063 fills the same fields, writes auto start 0 and starts playback with `sceAvPlayerStart`; a title that sets auto start has not been seen. A smaller `this_size` is read with the PS4 offsets. The thread parameters are ignored +- [AvPlayer auto start](../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - the auto start that `sceAvPlayerAddSource` queues for a player with auto start or without an event callback starts the player only while it is still ready: a title that starts the player itself before the auto start runs keeps its playback and the frames it holds. Not confirmed on a title. - [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info - [sceRazorCpuFlushOccurred](../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signature and return values (1 = flush since the last call, 0 = none) from [SharpProspero](https://github.com/SvenGDK/SharpProspero); without a capture nothing is flushed, so it returns 0 and writes 0 cycles to the optional out pointer (whether the real library writes it when nothing was flushed is unknown) - [sceRazorCpuPushMarkerStatic, sceRazorCpuPopMarker](../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signatures from PPSA26344's calls; (name, color, flags) assumed for the push; both do nothing without a capture