Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 45 additions & 4 deletions ps2xRuntime/src/lib/Kernel/Stubs/MPEG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,8 @@ namespace ps2_stubs
const uint8_t *data,
size_t size,
uint32_t guestAddr,
std::vector<MpegStreamCallbackEvent> &callbackEvents)
std::vector<MpegStreamCallbackEvent> &callbackEvents,
bool trackGuestAddrs = true)
{
if (!data || size == 0)
{
Expand Down Expand Up @@ -1153,10 +1154,13 @@ namespace ps2_stubs
playback.sawInput = true;

playback.pssBuffer.insert(playback.pssBuffer.end(), data, data + size);
playback.pssGuestAddrs.reserve(playback.pssGuestAddrs.size() + size);
for (size_t i = 0; i < size; ++i)
if (trackGuestAddrs)
{
playback.pssGuestAddrs.push_back(guestAddr + static_cast<uint32_t>(i));
playback.pssGuestAddrs.reserve(playback.pssGuestAddrs.size() + size);
for (size_t i = 0; i < size; ++i)
{
playback.pssGuestAddrs.push_back(guestAddr + static_cast<uint32_t>(i));
}
}
processPssBuffer(mpegAddr, playback, callbackEvents);
if (!playback.decodedFrames.empty())
Expand Down Expand Up @@ -1559,6 +1563,43 @@ namespace ps2_stubs
}
}

size_t feedMpegCdStreamBytes(const uint8_t *data, size_t size)
{
if (!data || size == 0u)
{
return 0u;
}

size_t routedCount = 0u;
{
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);

// Active only between notifyMpegCdStreamStart() and notifyMpegCdStreamEof().
const bool cdStreamActive =
g_mpeg_stub_state.cdStreamGeneration != 0u &&
!g_mpeg_stub_state.currentCdStreamEofSeen;
if (!cdStreamActive)
{
return 0u;
}

// Host-fed data carries no guest addresses, so no callback event is queued.
// TODO(host-feed callbacks): queue on MpegPlaybackState for a guest syscall to drain.
std::vector<MpegStreamCallbackEvent> callbackEvents;
for (auto &[mpegAddr, playback] : g_mpeg_stub_state.playbackByMpeg)
{
if (playback.cdStreamGeneration != g_mpeg_stub_state.cdStreamGeneration)
{
continue;
}
appendPssBytes(mpegAddr, playback, data, size, 0u, callbackEvents, false);
++routedCount;
}
}
g_mpeg_cv.notify_all();
return routedCount != 0u ? size : 0u;
}

void sceMpegFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
Expand Down
6 changes: 6 additions & 0 deletions ps2xRuntime/src/lib/Kernel/Stubs/MPEG.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ namespace ps2_stubs
void resetMpegStubState();
void notifyMpegCdStreamStart();
void notifyMpegCdStreamEof();
// Push `size` bytes of the active CD movie stream's program-stream/PES data
// into the guest-driven MPEG decoder from host memory. Must be bracketed by
// notifyMpegCdStreamStart()/notifyMpegCdStreamEof(). Returns `size` once the
// bytes are routed to the open decoder(s), or 0 when no CD stream is active.
// Thread-safe; stream callbacks are not dispatched on this path.
size_t feedMpegCdStreamBytes(const uint8_t *data, size_t size);
void sceMpegFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void sceMpegAddBs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void sceMpegAddCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
Expand Down
129 changes: 129 additions & 0 deletions ps2xTest/src/ps2_runtime_expansion_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,135 @@ void register_ps2_runtime_expansion_tests()
"repeated temporary starvation should continue advancing from the held frame");
});

tc.Run("host-fed CD stream decodes without a guest demux call", [](TestCase &t)
{
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
ps2_stubs::resetMpegStubState();

constexpr uint32_t kMpegAddr = 0x00123000u;
constexpr uint32_t kWorkAddr = 0x00140000u;
constexpr uint32_t kImageAddr = 0x00160000u;

R5900Context createCtx{};
setRegU32(createCtx, 4, kMpegAddr);
setRegU32(createCtx, 5, kWorkAddr);
setRegU32(createCtx, 6, 0x2000u);
ps2_stubs::sceMpegCreate(rdram.data(), &createCtx, nullptr);
t.IsTrue(::getRegU32(&createCtx, 2) != 0u,
"sceMpegCreate should return a nonzero handle");

ps2_stubs::notifyMpegCdStreamStart();

const std::vector<uint8_t> es = {
0x00u, 0x00u, 0x01u, 0xB3u, 0x01u, 0x00u, 0x10u, 0x12u, 0xFFu, 0xFFu, 0xE0u, 0x18u,
0x00u, 0x00u, 0x01u, 0xB5u, 0x14u, 0x8Au, 0x00u, 0x01u, 0x00u, 0x17u, 0x00u, 0x00u,
0x01u, 0xB8u, 0x00u, 0x08u, 0x00u, 0x40u, 0x00u, 0x00u, 0x01u, 0x00u, 0x00u, 0x0Fu,
0xFFu, 0xF8u, 0x00u, 0x00u, 0x01u, 0xB5u, 0x8Fu, 0xFFu, 0xF3u, 0x41u, 0x80u, 0x00u,
0x00u, 0x01u, 0x01u, 0x13u, 0xF8u, 0x7Du, 0x29u, 0x48u, 0x88u, 0x00u, 0x00u, 0x01u,
0xB3u, 0x01u, 0x00u, 0x10u, 0x12u, 0xFFu, 0xFFu, 0xE0u, 0x18u, 0x00u, 0x00u, 0x01u,
0xB5u, 0x14u, 0x8Au, 0x00u, 0x01u, 0x00u, 0x17u, 0x00u, 0x00u, 0x01u, 0xB8u, 0x00u,
0x08u, 0x00u, 0xC0u, 0x00u, 0x00u, 0x01u, 0x00u, 0x00u, 0x0Fu, 0xFFu, 0xF8u, 0x00u,
0x00u, 0x01u, 0xB5u, 0x8Fu, 0xFFu, 0xF3u, 0x41u, 0x80u, 0x00u, 0x00u, 0x01u, 0x01u,
0x13u, 0xF8u, 0x7Du, 0x29u, 0x48u, 0x88u, 0x00u, 0x00u, 0x01u, 0xB3u, 0x01u, 0x00u,
0x10u, 0x12u, 0xFFu, 0xFFu, 0xE0u, 0x18u, 0x00u, 0x00u, 0x01u, 0xB5u, 0x14u, 0x8Au,
0x00u, 0x01u, 0x00u, 0x17u, 0x00u, 0x00u, 0x01u, 0xB8u, 0x00u, 0x08u, 0x01u, 0x40u,
0x00u, 0x00u, 0x01u, 0x00u, 0x00u, 0x0Fu, 0xFFu, 0xF8u, 0x00u, 0x00u, 0x01u, 0xB5u,
0x8Fu, 0xFFu, 0xF3u, 0x41u, 0x80u, 0x00u, 0x00u, 0x01u, 0x01u, 0x13u, 0xF8u, 0x7Du,
0x29u, 0x48u, 0x88u};

std::vector<uint8_t> packet = {
0x00u, 0x00u, 0x01u, 0xE0u,
0x00u, static_cast<uint8_t>(es.size() + 3u),
0x80u, 0x00u, 0x00u};
packet.insert(packet.end(), es.begin(), es.end());

const size_t consumed = ps2_stubs::feedMpegCdStreamBytes(packet.data(), packet.size());
t.Equals(consumed, packet.size(),
"feedMpegCdStreamBytes should report the whole packet consumed");

ps2_stubs::notifyMpegCdStreamEof();

t.Equals(ps2_stubs::feedMpegCdStreamBytes(packet.data(), packet.size()), static_cast<size_t>(0u),
"feedMpegCdStreamBytes after CD EOF should be a no-op");

R5900Context pictureCtx{};
setRegU32(pictureCtx, 4, kMpegAddr);
setRegU32(pictureCtx, 5, kImageAddr);
ps2_stubs::sceMpegGetPicture(rdram.data(), &pictureCtx, nullptr);

t.Equals(Ps2FastRead32(rdram.data(), kMpegAddr + 0x00u), 16u,
"host-fed stream should decode to the expected frame width");
t.Equals(Ps2FastRead32(rdram.data(), kMpegAddr + 0x08u), 0u,
"host-fed stream should report frame index zero");
});

tc.Run("feedMpegCdStreamBytes before a CD stream start is a no-op", [](TestCase &t)
{
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
ps2_stubs::resetMpegStubState();

constexpr uint32_t kMpegAddr = 0x00123000u;
constexpr uint32_t kWorkAddr = 0x00140000u;

R5900Context createCtx{};
setRegU32(createCtx, 4, kMpegAddr);
setRegU32(createCtx, 5, kWorkAddr);
setRegU32(createCtx, 6, 0x2000u);
ps2_stubs::sceMpegCreate(rdram.data(), &createCtx, nullptr);
t.IsTrue(::getRegU32(&createCtx, 2) != 0u,
"sceMpegCreate should return a nonzero handle");

const std::vector<uint8_t> packet = {
0x00u, 0x00u, 0x01u, 0xE0u, 0x00u, 0x04u, 0x80u, 0x00u, 0x00u, 0xAAu};

t.Equals(ps2_stubs::feedMpegCdStreamBytes(packet.data(), packet.size()), static_cast<size_t>(0u),
"feedMpegCdStreamBytes with no active CD stream (handle exists but generation 0) should not consume bytes");
});

tc.Run("host feed fans out to every open decoder and reports the input range once", [](TestCase &t)
{
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
ps2_stubs::resetMpegStubState();

constexpr uint32_t kMpegA = 0x00123000u;
constexpr uint32_t kWorkA = 0x00140000u;
constexpr uint32_t kMpegB = 0x00133000u;
constexpr uint32_t kWorkB = 0x00150000u;

R5900Context createCtxA{};
setRegU32(createCtxA, 4, kMpegA);
setRegU32(createCtxA, 5, kWorkA);
setRegU32(createCtxA, 6, 0x2000u);
ps2_stubs::sceMpegCreate(rdram.data(), &createCtxA, nullptr);
t.IsTrue(::getRegU32(&createCtxA, 2) != 0u,
"sceMpegCreate should return a nonzero handle for decoder A");

R5900Context createCtxB{};
setRegU32(createCtxB, 4, kMpegB);
setRegU32(createCtxB, 5, kWorkB);
setRegU32(createCtxB, 6, 0x2000u);
ps2_stubs::sceMpegCreate(rdram.data(), &createCtxB, nullptr);
t.IsTrue(::getRegU32(&createCtxB, 2) != 0u,
"sceMpegCreate should return a nonzero handle for decoder B");

ps2_stubs::notifyMpegCdStreamStart();

const std::vector<uint8_t> programEnd = {0x00u, 0x00u, 0x01u, 0xB9u};
const size_t consumed = ps2_stubs::feedMpegCdStreamBytes(programEnd.data(), programEnd.size());
t.Equals(consumed, programEnd.size(),
"feedMpegCdStreamBytes should report the input range once, not per decoder");

R5900Context isEndCtxA{};
setRegU32(isEndCtxA, 4, kMpegA);
ps2_stubs::sceMpegIsEnd(rdram.data(), &isEndCtxA, nullptr);
t.Equals(getRegS32(isEndCtxA, 2), 1, "decoder A should have received the fed bytes");

R5900Context isEndCtxB{};
setRegU32(isEndCtxB, 4, kMpegB);
ps2_stubs::sceMpegIsEnd(rdram.data(), &isEndCtxB, nullptr);
t.Equals(getRegS32(isEndCtxB, 2), 1, "decoder B should have received the fed bytes");
});

tc.Run("sceMpegGetPicture releases an old waiter when the CD stream restarts", [](TestCase &t)
{
PS2Runtime runtime;
Expand Down