feat(mpeg): host-fed CD-stream ES injection for the guest-driven decoder - #178
feat(mpeg): host-fed CD-stream ES injection for the guest-driven decoder#178smmathews wants to merge 1 commit into
Conversation
|
I think I found a but on this PR but still checking |
Add feedMpegCdStreamBytes(), the missing data verb alongside the existing notifyMpegCdStreamStart/Eof control verbs. It lets an embedder that services the movie stream host-side (an HLE'd IOP streamer, or an on-disc stream transform applied before decode) push elementary-stream bytes straight into the guest-driven decoder instead of reimplementing the guest ring protocol or carrying a second, parallel decoder. Bytes route to every open decoder on the current CD-stream generation and demux through the same PSS path as sceMpegDemuxPss. There is no guest thread behind a host feed, so stream callbacks are not dispatched on this path yet; that gap is flagged with a TODO for a follow-up per-decoder event queue rather than silently dropped. Feed is order-insensitive with respect to sceMpegCreate. Previously, bytes fed before any decoder existed on a generation were silently lost: the routing loop found nothing to fan out to and returned 0, indistinguishable from "no active stream," and a decoder created afterward had no way to recover the missing prefix. Bytes fed before a decoder exists are now held in a bounded per-generation stage and replayed into the next decoder created on that generation before its first getPicture. The stage is discarded on overflow, stream stop, and stream restart, and the return value is `size` whenever a CD stream is active whether the bytes were routed live or staged, so a caller can no longer read "accepted but staged" as "rejected." Pure addition otherwise: a header constant plus one function declaration, one new public function, a defaulted parameter on the internal appendPssBytes, and nothing else in ps2xRuntime calls it — no existing call site or behavior changes for any current title.
377393a to
ef783c6
Compare
|
While auditing our side I did find one concrete bug, in case it's the same thing you hit: feedMpegCdStreamBytes only routed to decoders that already existed on the current CD-stream generation, so bytes fed after notifyMpegCdStreamStart but before the guest's first sceMpegCreate were silently dropped (return 0, no staging), and a decoder opened later never recovered that prefix. The return value also couldn't distinguish "no active stream" from "active but no decoder yet." I've fixed that in this PR (just pushed, rebased on current main): host-fed bytes now stage at the generation level (bounded, discarded on overflow/stop/restart rather than truncated) and replay to the next decoder that opens, and the return contract is now "size whenever a CD stream is active, routed or staged; 0 only when inactive", with regression tests for feed-before-open, the staging cap, and stage discard. If what you found is something else, the details would be very welcome. |
Host-fed CD-stream ES injection for the guest-driven MPEG decoder
Problem
Upstream's guest-driven MPEG decoder ingests elementary stream only through
the guest's own demux syscalls, which read from guest RAM at a guest
pointer. Runtimes that HLE the movie stream's IOP-side delivery — an IOP
streamer that fills an EE ring the runtime does not model — hold a correct,
in-order ES in host memory with the guest's
sceMpegGetPictureconsumerlive, but have no supported seam to hand those bytes to the decoder. They
are forced to reimplement the guest ring protocol or carry a parallel
downstream decoder. The same gap also applies when a runtime applies an
on-disc stream transform host-side before decode.
Fix
Add
size_t feedMpegCdStreamBytes(const uint8_t *, size_t): the missingdata verb to go with the existing control verbs
notifyMpegCdStreamStart/notifyMpegCdStreamEof. It routes host bytesinto the playback state(s) on the current CD-stream generation and demuxes
them through the exact same PSS path as
sceMpegDemuxPss, fanning out toevery open decoder on that generation. No
mpegAddrargument — a hoststreamer does not know the guest handle; routing mirrors the existing
generation bookkeeping. Returns
sizewhile a CD stream is active — whetherthe bytes are routed to the open decoder(s) or staged for a decoder not yet
created on this generation — and 0 only when no CD stream is active.
Feed ordering / staging
Host feed is order-insensitive with respect to
sceMpegCreate. Bytes fedafter
notifyMpegCdStreamStartbut before a decoder exists on the currentCD-stream generation are staged in host memory (bounded by a fixed cap; on
overflow the stage is discarded and a later decoder resyncs on live feed)
and replayed into the next decoder created on that generation before its
first
getPicture. Staged bytes are discarded on stream stop or restart.This closes a silent-drop window where bytes fed before the first
sceMpegCreatewere dropped and the later decoder could not recover theprefix. A single decoder is the supported shape; a second decoder that opens
mid-generation after bytes have already routed to the first resyncs at the
next sequence header, as with guest demux.
Scope / why generic
No game knowledge: no file layout, no transform, no addresses. Symmetric
with and reusing the merged
notifyMpegCdStream*contract. Inert for everyexisting title (no call site invokes it; it early-outs when no CD stream is
active). Internally a pure addition: one header declaration, one new public
function, and a single defaulted parameter on the internal
appendPssBytesthat leaves all existing callers and the guest demux/callback path
unchanged.
No in-tree consumer
Nothing in
ps2xRuntimecalls this yet. It is an embedder seam for theruntime library — the missing data verb alongside the existing
notifyMpegCdStream*control verbs — so downstream embedders thatproduce the elementary stream host-side can feed the upstream decoder
instead of carrying a parallel one.
Maintainer-attention point (callback design)
A host feed has no calling guest thread, so guest stream callbacks cannot be
dispatched from it. This PR ships the drop-callbacks variant (documented
with
// TODO(host-feed callbacks)): host-fed data records no guestaddresses, so no callback event is ever queued. If a future title needs
stream callbacks on a host-fed stream, the follow-up is a small
per-
MpegPlaybackStateevent queue drained by the next guest MPEG syscall.Flagged for your call.
Testing
Full suite passes — run:
The original routing-contract tests are kept as-is: a start/feed/getPicture/
eof round-trip that decodes a host-fed program stream with no
sceMpegDemuxPss*call in the path, a feed-before-start no-op, and atwo-decoder fan-out test that pins the input range being reported once (not
once per decoder) and that every open decoder on the current generation
receives the fed bytes. New tests cover the pre-decoder staging path added
in this revision: a feed staged before any decoder exists reaching the
decoder created afterward (and reporting the feed as accepted, not dropped),
a packet split across two feed calls, decoding across a CD-stream restart, a
staging-cap overflow with a per-generation recovery leg, and a staged-but-
never-consumed feed being discarded at stream stop rather than resurrected by
a decoder created after EOF. The new tests require an ffmpeg-enabled build,
the same as every existing MPEG decode test.
Each pinned property has an isolating source-level mutation. Two properties
share pre-existing partial-buffer machinery and fail as a pair rather than
singly, and one defensive restart guard is provably redundant (no public-API
mutation isolates it); these non-isolating cases are expected and were
verified deliberately, not glossed. Full mutation matrix available on
request.
Note on the return value: it is
size(the whole input range) while a CDstream is active, whether the bytes are routed live or staged, and 0 only
when no CD stream is active — it is not a decoded-byte count and does not
scale with how many decoders are open.