fix(avplayer): do not let auto start restart playback the title started
sceAvPlayerAddSource queues an auto start for a player created with auto start or without an event callback. When the title enabled its streams and called sceAvPlayerStart before the controller thread ran it, the auto start called Start again on the playing player, which stopped it, released every video buffer, including the frame the title held, and started the film over. guest_avplayer occasionally caught this as "video frame is not a texture allocation"; right after the test's own start, 8 texture buffers were released in 15 of 15 runs. The auto start now starts the player only while it is still ready, and checks that under the player lock together with the start. TestAutoStartKeepsStartedPlayback starts a callback-less player itself and checks that no texture buffer is released afterwards and that the frame it holds stays allocated. It fails on every run without the fix.
This commit is contained in:
parent
57f96fd2b3
commit
9fc0af0b10
|
|
@ -130,6 +130,8 @@ private:
|
|||
void deliver(const Event& event);
|
||||
void autoStart();
|
||||
void checkEndOfFile();
|
||||
bool readyLocked() const;
|
||||
int startLocked();
|
||||
int stopLocked();
|
||||
|
||||
AvPlayerFileReplacement file{};
|
||||
|
|
|
|||
|
|
@ -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<std::uint32_t>(video));
|
||||
if (audio != -1) EnableStream(static_cast<std::uint32_t>(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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue