Skip to content

scePsmfPlayer (hle-psmf-player.ts)

Implements scePsmfPlayer, the high-level cutscene playback API, and the scePsmf container parsing calls. Unlike sceMpeg, this path does real video: it bridges the game's calls to the WebCodecs-based PsmfDecoder, converts decoded frames to the PSP pixel format, and writes them to VRAM or RAM. Video decode needs the browser (WebCodecs); see Audio & Media.

A player is identified by a context address in game memory. Create writes a small integer id at that address, and every other call reads it back to find the player instance.

Typical flow

scePsmfPlayerCreate, then scePsmfPlayerSetPsmf(filename) (or the Offset / CB variants), then scePsmfPlayerStart, then a loop of scePsmfPlayerUpdate and scePsmfPlayerGetVideoData / scePsmfPlayerGetAudioData, then scePsmfPlayerStop and scePsmfPlayerDelete.

Player setup

SignatureWhat it does
scePsmfPlayerCreate(psmfPlayer: u32, dataPtr: u32): intAllocates a player instance, writes its id to psmfPlayer, and sets status to INIT. dataPtr (init params) is ignored here.
scePsmfPlayerSetPsmf(psmfPlayer: u32, filename: const char*): intLooks the PMF file up in the virtual filesystem, hands its bytes to a new PsmfDecoder, and kicks off async decode. Sets status to STANDBY on success, ERROR if the file is missing.
scePsmfPlayerSetPsmfCB(psmfPlayer: u32, filename: const char*): intSame as SetPsmf; the real PSP variant runs callbacks while loading. We use the identical handler with no separate callback behavior.
scePsmfPlayerSetPsmfOffset(psmfPlayer: u32, filename: const char*, offset: int): intSame handler as SetPsmf. The offset argument is not used (we always load the file from its start).
scePsmfPlayerSetPsmfOffsetCB(psmfPlayer: u32, filename: const char*, offset: int): intSame handler as SetPsmf; callback and offset arguments are not used.
scePsmfPlayerSetTempBuf(psmfPlayer: u32, tempBufAddr: u32, tempBufSize: u32): intNo-op stub that returns 0. We do not use the game's temp buffer; decode runs through WebCodecs.
scePsmfPlayerConfigPlayer(psmfPlayer: u32, configMode: int, configAttr: int): u32No-op stub that returns 0. Player config (looping mode, pixel format) is ignored.

Playback

SignatureWhat it does
scePsmfPlayerStart(psmfPlayer: u32, psmfPlayerData: u32, initPts: int): intResets the frame cursor to 0 and sets status to PLAYING. psmfPlayerData and initPts are ignored; playback always starts at the first frame.
scePsmfPlayerUpdate(psmfPlayer: u32): intAdvances the frame cursor by one while PLAYING and the decode has finished, and flips status to FINISHED once the cursor reaches the last frame.
scePsmfPlayerGetVideoData(psmfPlayer: u32, videoDataAddr: u32): intReads the videoData struct (frameWidth, displayBuf pointer, then PTS high/low), writes the current decoded frame to displayBuf as ABGR8888, and writes the frame PTS back into the struct. Returns an error before decode is ready.
scePsmfPlayerGetAudioData(psmfPlayer: u32, audioDataAddr: u32): intWrites one 2048-sample stereo frame (8192 bytes) of decoded cutscene audio, advancing the audio cursor in lockstep with the video. Falls back to silence when the audio track is not decoded yet (or has none). Returns 0. The cutscene audio is decoded to PCM through FFmpeg WASM in the browser; headless has no audio.
scePsmfPlayerGetAudioOutSize(psmfPlayer: u32): intReturns the fixed PSP audio buffer size, 8192 bytes (2048 stereo samples).
scePsmfPlayerStop(psmfPlayer: u32): intSets status back to STANDBY and resets the frame cursor to 0.
scePsmfPlayerBreak(psmfPlayer: u32): intSets status back to STANDBY (stops playback without resetting the cursor).
scePsmfPlayerChangePlayMode(psmfPlayer: u32, playMode: int, playSpeed: int): u32No-op stub that returns 0. Play mode and speed changes are ignored.
scePsmfPlayerReleasePsmf(psmfPlayer: u32): intDrops the decoder and decoded frames and sets status back to INIT, so a new PMF can be loaded into the same player.
scePsmfPlayerDelete(psmfPlayer: u32): intFrees the readback canvas and removes the player instance from the map.

State and selection

SignatureWhat it does
scePsmfPlayerGetCurrentStatus(psmfPlayer: u32): intReturns the player's PSP status code (INIT, STANDBY, PLAYING, FINISHED), or ERROR if the player is unknown, so the game's state machine advances.
scePsmfPlayerGetCurrentPts(psmfPlayer: u32, currentPtsAddr: u32): u32Writes the current frame's PTS (low 32 bits) to currentPtsAddr. Returns an error before decode is ready.
scePsmfPlayerGetPsmfInfo(psmfPlayer: u32, psmfInfoAddr: u32, widthAddr: u32, heightAddr: u32): u32Writes video width, height, and frame count into the struct at psmfInfoAddr. Our handler uses only the first pointer argument and does not fill the separate widthAddr / heightAddr outputs.
scePsmfPlayerGetCurrentPlayMode(psmfPlayer: u32, playModeAddr: u32, playSpeedAddr: u32): u32Writes a fixed normal play mode (0) and 1x speed (1) to the given pointers.
scePsmfPlayerSelectVideo(psmfPlayer: u32): u32No-op stub that returns 0. We always play the single decoded video stream.
scePsmfPlayerSelectAudio(psmfPlayer: u32): u32No-op stub that returns 0.
scePsmfPlayerSelectSpecificVideo(psmfPlayer: u32, videoCodec: int, videoStreamNum: int): u32No-op stub that returns 0. Stream selection is ignored.
scePsmfPlayerSelectSpecificAudio(psmfPlayer: u32, audioCodec: int, audioStreamNum: int): u32No-op stub that returns 0.
scePsmfPlayerGetCurrentVideoStream(psmfPlayer: u32, videoCodecAddr: u32, videoStreamNumAddr: u32): u32Writes 0 for both codec and stream number to the given pointers.
scePsmfPlayerGetCurrentAudioStream(psmfPlayer: u32, audioCodecAddr: u32, audioStreamNumAddr: u32): u32Writes 0 for both codec and stream number to the given pointers.
scePsmfPlayer_340C12CB(psmfPlayer: u32): u32Unnamed in PPSSPP, so the real signature is unknown beyond the player context arg. Our handler is a no-op that returns 0.

scePsmf container parsing

These calls inspect a PSMF container and report its streams and timing. Most are no-op stubs that return 0; the few that return a useful constant are noted.

SignatureWhat it does
scePsmfSetPsmf(psmfStruct: u32, psmfData: u32): u32No-op stub that returns 0. We do not parse the container header into the game's struct.
scePsmfVerifyPsmf(psmfAddr: u32): u32No-op stub that returns 0 (reports the data as a valid PSMF).
scePsmfGetHeaderSize(psmfStruct: u32, sizeAddr: u32): u32Returns the header size 0x800 directly in v0. PPSSPP writes it to sizeAddr; our handler ignores that pointer.
scePsmfGetStreamSize(psmfStruct: u32, sizeAddr: u32): u32Returns 0 directly in v0. As with GetHeaderSize, the sizeAddr pointer is not written.
scePsmfGetNumberOfStreams(psmfStruct: u32): intReturns 1 (we report a single stream).
scePsmfGetNumberOfEPentries(psmfStruct: u32): u32No-op stub that returns 0 (no entry-point table entries).
scePsmfGetVideoInfo(psmfStruct: u32, videoInfoAddr: u32): intNo-op stub that returns 0; no video info is written to the struct.
scePsmfGetAudioInfo(psmfStruct: u32, audioInfoAddr: u32): intNo-op stub that returns 0; no audio info is written to the struct.
scePsmfGetPresentationStartTime(psmfStruct: u32, startTimeAddr: u32): u32No-op stub that returns 0; the start-time pointer is not written.
scePsmfGetPresentationEndTime(psmfStruct: u32, endTimeAddr: u32): u32No-op stub that returns 0; the end-time pointer is not written.
scePsmfGetCurrentStreamType(psmfStruct: u32, typeAddr: u32, channelAddr: u32): u32No-op stub that returns 0; the type and channel pointers are not written.
scePsmfSpecifyStreamWithStreamType(psmfStruct: u32, streamType: u32, channel: u32): intNo-op stub that returns 0. Stream selection by type is ignored.

Summary

All scePsmfPlayer and scePsmf calls have real register() handlers (none use kernel.stub()), but several are no-op handlers that just return 0 as noted above. The video path (SetPsmf, Start, Update, GetVideoData) runs through PsmfDecoder, and the cutscene audio path (GetAudioData) decodes through FFmpeg WASM, both browser-only. The scePsmf container-parsing calls are not really implemented (they return fixed constants or 0).