diff --git a/core/libs/prx/libSceAvPlayer/Export.cpp b/core/libs/prx/libSceAvPlayer/Export.cpp index 25dfc65c5..fc78bf88b 100644 --- a/core/libs/prx/libSceAvPlayer/Export.cpp +++ b/core/libs/prx/libSceAvPlayer/Export.cpp @@ -2,7 +2,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include +#include #include #include "SceTypes.hpp" #include "prx/libSceAvPlayer/include/AvPlayer.hpp" @@ -19,6 +21,10 @@ std::uint32_t VideoBufferCount(std::int32_t requested) { return static_cast(std::clamp(requested, 2, 16)); } +constexpr std::size_t Ps5InitDataExSize = 0x230; +constexpr std::size_t Ps5AutoStartOffset = 0x74; +constexpr std::size_t Ps5VideoFrameBuffersOffset = 0x228; + } #pragma GCC visibility push(default) @@ -97,8 +103,14 @@ int APS5_VABI sceAvPlayerInitEx(const AvPlayerInitDataEx* init_ex, AvPlayerInter init.file_replacement = init_ex->file_replacement; init.event_replacement = init_ex->event_replacement; init.debug_level = init_ex->debug_level; - init.num_output_video_framebuffers = init_ex->num_output_video_framebuffers; - init.auto_start = init_ex->auto_start; + if (init_ex->this_size >= Ps5InitDataExSize) { + const auto* bytes = reinterpret_cast(init_ex); + std::memcpy(&init.num_output_video_framebuffers, bytes + Ps5VideoFrameBuffersOffset, sizeof(init.num_output_video_framebuffers)); + init.auto_start = bytes[Ps5AutoStartOffset] != 0; + } else { + init.num_output_video_framebuffers = init_ex->num_output_video_framebuffers; + init.auto_start = init_ex->auto_start; + } init.default_language = init_ex->default_language; *handle = new Player(init, VideoBufferCount(init.num_output_video_framebuffers)); return SCE_OK; diff --git a/core/libs/tests/GuestAvPlayer.cpp b/core/libs/tests/GuestAvPlayer.cpp index b38ee2895..b89d0776f 100644 --- a/core/libs/tests/GuestAvPlayer.cpp +++ b/core/libs/tests/GuestAvPlayer.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -993,6 +994,46 @@ void TestHandedOutFramesStayIntact() { Check(sceAvPlayerClose(player) == 0, "close failed"); } +void TestPs5ExtendedInitLayout() { + constexpr std::size_t Ps5Size = 0x230; + constexpr std::size_t Ps5AutoStart = 0x74; + constexpr std::size_t Ps5ThreadParameters = 0x78; + constexpr std::size_t Ps5VideoFrameBuffers = 0x228; + constexpr std::int32_t Buffers = 3; + constexpr int DecodeAheadBuffers = 4; + static_assert(offsetof(AvPlayerInitDataEx, audio_decoder_priority) == Ps5AutoStart); + const auto run = [&](bool autoStart) { + Events events; + const AvPlayerInitData common = InitData(&events); + AvPlayerInitDataEx head{}; + head.this_size = Ps5Size; + head.memory_replacement = common.memory_replacement; + head.event_replacement = common.event_replacement; + head.default_language = common.default_language; + alignas(AvPlayerInitDataEx) std::array raw{}; + std::memcpy(raw.data(), &head, Ps5AutoStart); + raw[Ps5AutoStart] = autoStart ? 1 : 0; + if (!autoStart) std::fill(raw.begin() + static_cast(Ps5ThreadParameters), raw.end(), std::uint8_t{1}); + std::memcpy(raw.data() + Ps5VideoFrameBuffers, &Buffers, sizeof(Buffers)); + AvPlayerInternal* player = nullptr; + Check(sceAvPlayerInitEx(reinterpret_cast(raw.data()), &player) == 0 && player != nullptr, "PS5 extended init failed"); + Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4") == 0, "add source after the PS5 extended init failed"); + if (autoStart) { + Check(WaitFor([&] { return events.Seen(EventPlay); }), "auto start at 0x74 of the PS5 extended init did not start playback"); + } else { + Check(WaitFor([&] { return events.Seen(EventReady); }), "ready event missing after the PS5 extended init"); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + Check(!events.Seen(EventPlay), "playback started although auto start at 0x74 of the PS5 extended init is 0"); + Check(sceAvPlayerEnableStream(player, VideoStream) == 0, "enable video after the PS5 extended init failed"); + Check(sceAvPlayerStart(player) == 0, "start after the PS5 extended init failed"); + } + Check(WaitFor([&] { return TextureCount() == Buffers + DecodeAheadBuffers; }), "video frame buffer count at 0x228 of the PS5 extended init not used"); + Check(sceAvPlayerClose(player) == 0, "close after the PS5 extended init failed"); + }; + run(false); + run(true); +} + } int main() { @@ -1005,6 +1046,7 @@ int main() { TestOptionalVideoBuffersRespectMemoryLimit(); TestFileReplacementAutoStart(); TestHandedOutFramesStayIntact(); + TestPs5ExtendedInitLayout(); std::puts("AvPlayer tests passed"); return 0; } catch (const std::exception& error) { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 8f2178189..3c9fc36b1 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -171,6 +171,7 @@ Throughout the project, every function at every stage either **does exactly what - [NGS2 streaming PCM waveforms](../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - PPSA21402 streams its music by adding one block whose `num_samples` is the whole track and whose `data_size` is a 16 KiB first piece, with `CONTINUE`, and then adds each following piece with flag `0x2` (called `APPEND` here) and the same `num_samples`. No reference describes flag `0x2`. A 16-bit PCM block without skipped samples or repeats whose `num_samples` exceed its data is assumed to start such a stream; flag `0x2` is assumed to append the block's data to the last streaming block, ignoring its `num_samples`; the voice is assumed to output silence without advancing while it has played all appended data and `CONTINUE` is set; and the waveform is assumed to end at `num_samples` or, once `CONTINUE` is cleared, at the end of the appended data. `sceNgs2VoiceGetState` reports the bytes played as `decoded_data_size`, which the title uses to release its queued data, and the read position as `waveform_data`. Appending to a block that is not a stream, or other formats, skipped samples, repeats and partial frames, throw - [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 - [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