#include "Common.h" #include "MPEG.h" #include "runtime/ee_scheduler.h" #if !defined(PS2X_HAS_FFMPEG) #define PS2X_HAS_FFMPEG 1 #endif #if PS2X_HAS_FFMPEG extern "C" { #include #include #include #include } #endif #include #include #include "Syscalls/Helpers/State.h" namespace ps2_stubs { namespace { struct MpegDecodedFrame { int width = 0; int height = 0; std::vector rgba; }; #if PS2X_HAS_FFMPEG std::string ffmpegErrorString(int err) { std::array buffer{}; if (av_strerror(err, buffer.data(), buffer.size()) < 0) { return "unknown FFmpeg error"; } return std::string(buffer.data()); } void configureFfmpegLogLevel() { static std::once_flag s_once; std::call_once(s_once, [] { #if AGRESSIVE_LOGS av_log_set_level(AV_LOG_WARNING); #else av_log_set_level(AV_LOG_ERROR); #endif }); } class MpegFfmpegDecoder { public: MpegFfmpegDecoder() = default; ~MpegFfmpegDecoder() { reset(); } MpegFfmpegDecoder(const MpegFfmpegDecoder &) = delete; MpegFfmpegDecoder &operator=(const MpegFfmpegDecoder &) = delete; bool feed(const uint8_t *data, size_t size, std::deque &frames) { if (!data || size == 0) { return true; } if (!ensureInitialized()) { return false; } static uint32_t s_feedLogCount = 0u; const bool shouldLog = (s_feedLogCount < 32u); if (shouldLog) ++s_feedLogCount; size_t totalParsed = 0u; size_t totalPacketsSent = 0u; size_t framesBefore = frames.size(); const uint8_t *cursor = data; size_t remaining = size; while (remaining > 0) { uint8_t *packetData = nullptr; int packetSize = 0; const int chunk = static_cast(std::min( remaining, static_cast(std::numeric_limits::max()))); const int used = av_parser_parse2( m_parser, m_codecCtx, &packetData, &packetSize, cursor, chunk, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); if (used < 0) { std::cerr << "[MPEG] parser failed: " << ffmpegErrorString(used) << std::endl; return false; } if (used == 0 && packetSize == 0) { break; } totalParsed += static_cast(used); cursor += used; remaining -= static_cast(used); if (packetSize > 0) { ++totalPacketsSent; if (!sendPacket(packetData, static_cast(packetSize), frames)) { return false; } } } if (shouldLog) { PS2_IF_AGRESSIVE_LOGS({ std::cerr << "[MPEG:feed] inSize=" << size << " parsed=" << totalParsed << " packets=" << totalPacketsSent << " newFrames=" << (frames.size() - framesBefore) << " totalFrames=" << frames.size() << std::endl; }); } return true; } bool flush(std::deque &frames) { if (!m_initialized || m_drained) { return true; } if (m_parser) { uint8_t *packetData = nullptr; int packetSize = 0; const int used = av_parser_parse2( m_parser, m_codecCtx, &packetData, &packetSize, nullptr, 0, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); (void)used; if (packetSize > 0 && !sendPacket(packetData, static_cast(packetSize), frames)) { return false; } } const int sendRet = avcodec_send_packet(m_codecCtx, nullptr); if (sendRet < 0 && sendRet != AVERROR_EOF) { std::cerr << "[MPEG] decoder flush failed: " << ffmpegErrorString(sendRet) << std::endl; return false; } const bool ok = receiveFrames(frames); m_drained = true; return ok; } void reset() { if (m_swsCtx) { sws_freeContext(m_swsCtx); m_swsCtx = nullptr; } if (m_frame) { av_frame_free(&m_frame); } if (m_packet) { av_packet_free(&m_packet); } if (m_codecCtx) { avcodec_free_context(&m_codecCtx); } if (m_parser) { av_parser_close(m_parser); m_parser = nullptr; } m_swsWidth = 0; m_swsHeight = 0; m_swsFormat = AV_PIX_FMT_NONE; m_initialized = false; m_drained = false; } private: bool ensureInitialized() { if (m_initialized) { return true; } configureFfmpegLogLevel(); const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO); if (!codec) { std::cerr << "[MPEG] FFmpeg MPEG-2 decoder not found." << std::endl; return false; } m_parser = av_parser_init(AV_CODEC_ID_MPEG2VIDEO); if (!m_parser) { std::cerr << "[MPEG] FFmpeg MPEG-video parser not found." << std::endl; return false; } m_codecCtx = avcodec_alloc_context3(codec); m_frame = av_frame_alloc(); m_packet = av_packet_alloc(); if (!m_codecCtx || !m_frame || !m_packet) { std::cerr << "[MPEG] failed to allocate FFmpeg decoder state." << std::endl; reset(); return false; } m_codecCtx->thread_count = 1; m_codecCtx->skip_frame = AVDISCARD_NONKEY; m_codecCtx->err_recognition = 0; const int ret = avcodec_open2(m_codecCtx, codec, nullptr); if (ret < 0) { std::cerr << "[MPEG] failed to open MPEG decoder: " << ffmpegErrorString(ret) << std::endl; reset(); return false; } m_initialized = true; m_drained = false; m_seenKeyframe = false; return true; } bool sendPacket(const uint8_t *data, size_t size, std::deque &frames) { if (!data || size == 0) { return true; } av_packet_unref(m_packet); const int allocRet = av_new_packet(m_packet, static_cast(size)); if (allocRet < 0) { std::cerr << "[MPEG] failed to allocate packet: " << ffmpegErrorString(allocRet) << std::endl; return false; } std::memcpy(m_packet->data, data, size); int ret = avcodec_send_packet(m_codecCtx, m_packet); if (ret == AVERROR(EAGAIN)) { if (!receiveFrames(frames)) { av_packet_unref(m_packet); return false; } ret = avcodec_send_packet(m_codecCtx, m_packet); } av_packet_unref(m_packet); if (ret < 0 && ret != AVERROR(EAGAIN)) { static uint32_t s_rejectedPacketLogCount = 0u; if (s_rejectedPacketLogCount < 32u) { std::cerr << "[MPEG] decoder rejected packet, dropping: " << ffmpegErrorString(ret) << std::endl; ++s_rejectedPacketLogCount; } return true; } return receiveFrames(frames); } bool receiveFrames(std::deque &frames) { while (true) { const int ret = avcodec_receive_frame(m_codecCtx, m_frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { return true; } if (ret < 0) { static uint32_t s_receiveErrorLogCount = 0u; if (s_receiveErrorLogCount < 32u) { std::cerr << "[MPEG] decoder receive failed, dropping: " << ffmpegErrorString(ret) << std::endl; ++s_receiveErrorLogCount; } return true; } if (!m_seenKeyframe) { m_seenKeyframe = true; m_codecCtx->skip_frame = AVDISCARD_DEFAULT; } if (!convertFrame(frames)) { av_frame_unref(m_frame); return false; } av_frame_unref(m_frame); } } bool convertFrame(std::deque &frames) { const int width = m_frame->width; const int height = m_frame->height; const AVPixelFormat srcFormat = static_cast(m_frame->format); if (width <= 0 || height <= 0 || srcFormat == AV_PIX_FMT_NONE) { return false; } if (!m_swsCtx || m_swsWidth != width || m_swsHeight != height || m_swsFormat != srcFormat) { if (m_swsCtx) { sws_freeContext(m_swsCtx); m_swsCtx = nullptr; } m_swsCtx = sws_getContext( width, height, srcFormat, width, height, AV_PIX_FMT_RGBA, SWS_BILINEAR, nullptr, nullptr, nullptr); if (!m_swsCtx) { std::cerr << "[MPEG] failed to create FFmpeg scaler." << std::endl; return false; } m_swsWidth = width; m_swsHeight = height; m_swsFormat = srcFormat; } MpegDecodedFrame decoded; decoded.width = width; decoded.height = height; decoded.rgba.resize(static_cast(width) * static_cast(height) * 4u); uint8_t *dstData[4] = {decoded.rgba.data(), nullptr, nullptr, nullptr}; int dstLinesize[4] = {width * 4, 0, 0, 0}; const int scaledRows = sws_scale( m_swsCtx, m_frame->data, m_frame->linesize, 0, height, dstData, dstLinesize); if (scaledRows <= 0) { std::cerr << "[MPEG] FFmpeg scaler produced no rows." << std::endl; return false; } frames.push_back(std::move(decoded)); return true; } AVCodecParserContext *m_parser = nullptr; AVCodecContext *m_codecCtx = nullptr; AVFrame *m_frame = nullptr; AVPacket *m_packet = nullptr; SwsContext *m_swsCtx = nullptr; int m_swsWidth = 0; int m_swsHeight = 0; AVPixelFormat m_swsFormat = AV_PIX_FMT_NONE; bool m_initialized = false; bool m_drained = false; bool m_seenKeyframe = false; }; #else // TODO class MpegFfmpegDecoder { public: bool feed(const uint8_t *, size_t, std::deque &) { static bool s_warnedNoFfmpeg = false; if (!s_warnedNoFfmpeg) { std::cerr << "[MPEG] runtime built without FFmpeg; MPEG video decode is disabled." << std::endl; s_warnedNoFfmpeg = true; } return false; } bool flush(std::deque &) { return true; } void reset() {} }; #endif struct MpegRegisteredCallback { uint32_t type = 0u; uint32_t streamId = 0u; uint32_t func = 0u; uint32_t data = 0u; uint32_t handle = 0u; bool stream = false; }; struct MpegPlaybackState { uint32_t picturesServed = 0u; uint32_t width = 320u; uint32_t height = 240u; uint32_t decodeMode = 0u; uint32_t imageBufferAddr = 0u; bool sawInput = false; bool streamEnded = false; bool decoderFailed = false; uint64_t cdStreamGeneration = 0u; bool waitingForVideoSequenceHeader = true; std::vector videoSequenceSyncBuffer; std::vector pssBuffer; std::vector pssGuestAddrs; std::deque decodedFrames; std::unique_ptr decoder; uint64_t pictureIntervalQ32 = 0u; uint64_t nextPictureTickQ32 = std::numeric_limits::max(); }; struct MpegStreamCallbackEvent { uint32_t mpegAddr = 0u; uint32_t streamType = 0u; uint32_t dataAddr = 0u; uint32_t len = 0u; uint64_t pts = 0xFFFFFFFFFFFFFFFFull; uint64_t dts = 0xFFFFFFFFFFFFFFFFull; std::vector callbacks; }; struct MpegStubState { bool initialized = false; uint32_t nextCallbackHandle = 1u; uint64_t cdStreamGeneration = 0u; uint64_t cdStreamBytesProduced = 0u; uint64_t cdStreamBytesDemuxed = 0u; bool cdStreamEofPending = false; bool currentCdStreamEofSeen = false; uint32_t feedEsTraceCount = 0u; uint32_t demuxPssTraceCount = 0u; uint32_t demuxRingTraceCount = 0u; uint32_t getPictureWaitTraceCount = 0u; uint32_t pictureTraceCount = 0u; uint32_t isEndTraceCount = 0u; std::unordered_map> callbacksByMpeg; std::unordered_map playbackByMpeg; }; std::mutex g_mpeg_stub_mutex; constexpr uint32_t kMpegPictureWaitType = 1u; MpegStubState g_mpeg_stub_state; // TODO this resolution should follow runtime resolution constexpr uint32_t kStubMovieWidth = 320u; constexpr uint32_t kStubMovieHeight = 240u; constexpr uint32_t kMpegStrM2V = 0u; constexpr uint32_t kMpegStrPCM = 1u; constexpr uint32_t kMpegStrADPCM = 2u; constexpr uint8_t kMpegPackHeader = 0xBAu; constexpr uint8_t kMpegSystemHeader = 0xBBu; constexpr uint8_t kMpegProgramEnd = 0xB9u; constexpr uint8_t kMpegSequenceEnd = 0xB7u; constexpr uint8_t kMpegPrivateStream1 = 0xBDu; constexpr size_t kStartCodeNotFound = std::numeric_limits::max(); constexpr uint32_t kMpegCallbackDataSize = 0x20u; constexpr uint64_t kPictureClockOne = 1ull << 32u; uint64_t mpegPictureIntervalQ32(uint8_t frameRateCode) { uint64_t frameRateNumerator = 0u; uint64_t frameRateDenominator = 1u; switch (frameRateCode) { case 1u: frameRateNumerator = 24000u; frameRateDenominator = 1001u; break; case 2u: frameRateNumerator = 24u; break; case 3u: frameRateNumerator = 25u; break; case 4u: frameRateNumerator = 30000u; frameRateDenominator = 1001u; break; case 5u: frameRateNumerator = 30u; break; case 6u: frameRateNumerator = 50u; break; case 7u: frameRateNumerator = 60000u; frameRateDenominator = 1001u; break; case 8u: frameRateNumerator = 60u; break; default: return 0u; } // EE VSync runs at the NTSC field rate. Q32 preserves fractional // cadences such as 24 fps without accumulating host-time drift. const uint64_t denominator = 1001u * frameRateNumerator; const uint64_t numerator = (60000u * frameRateDenominator) << 32u; return std::max(kPictureClockOne, (numerator + denominator / 2u) / denominator); } uint32_t align16(uint32_t value) { return (value + 15u) & ~15u; } uint32_t readStackArg(uint8_t *rdram, R5900Context *ctx, uint32_t offset) { if (!rdram || !ctx) { return 0u; } return FAST_READ32(getRegU32(ctx, 29) + offset); } uint32_t readAbiArg4(uint8_t *rdram, R5900Context *ctx) { const uint32_t regArg = getRegU32(ctx, 8); if (regArg != 0u) { return regArg; } return readStackArg(rdram, ctx, 0x10u); } MpegPlaybackState &getPlaybackState(uint32_t mpegAddr) { return g_mpeg_stub_state.playbackByMpeg[mpegAddr]; } MpegPlaybackState makeFreshPlaybackState() { MpegPlaybackState playback{}; playback.cdStreamGeneration = g_mpeg_stub_state.cdStreamGeneration; return playback; } MpegPlaybackState makeFreshPlaybackStatePreservingConfig(const MpegPlaybackState &oldPlayback) { MpegPlaybackState playback = makeFreshPlaybackState(); playback.decodeMode = oldPlayback.decodeMode; playback.imageBufferAddr = oldPlayback.imageBufferAddr; playback.width = oldPlayback.width; playback.height = oldPlayback.height; return playback; } uint16_t readBe16(const uint8_t *p) { return static_cast((static_cast(p[0]) << 8u) | static_cast(p[1])); } bool isVideoStreamId(uint8_t streamId) { return streamId >= 0xE0u && streamId <= 0xEFu; } bool isAudioStreamId(uint8_t streamId) { return streamId == kMpegPrivateStream1 || (streamId >= 0xC0u && streamId <= 0xDFu); } bool isLengthPrefixedHeader(uint8_t streamId) { switch (streamId) { case kMpegSystemHeader: case 0xBCu: // program_stream_map case 0xBEu: // padding_stream case 0xBFu: // private_stream_2 case 0xF0u: // ECM case 0xF1u: // EMM case 0xF2u: // DSMCC case 0xF8u: // ITU-T H.222.1 type E case 0xFFu: // program_stream_directory return true; default: return false; } } size_t findStartCode(const std::vector &buffer, size_t from) { if (buffer.size() < 4 || from >= buffer.size() - 3u) { return kStartCodeNotFound; } for (size_t i = from; i + 3u < buffer.size(); ++i) { if (buffer[i] == 0x00u && buffer[i + 1u] == 0x00u && buffer[i + 2u] == 0x01u) { return i; } } return kStartCodeNotFound; } bool containsMpegSequenceEnd(const uint8_t *data, size_t size) { if (!data || size < 4) { return false; } for (size_t i = 0; i + 3u < size; ++i) { if (data[i] == 0x00u && data[i + 1u] == 0x00u && data[i + 2u] == 0x01u && data[i + 3u] == kMpegSequenceEnd) { return true; } } return false; } size_t findMpegSequenceHeader(const uint8_t *data, size_t size) { if (!data || size < 8u) { return kStartCodeNotFound; } for (size_t i = 0; i + 7u < size; ++i) { if (data[i] == 0x00u && data[i + 1u] == 0x00u && data[i + 2u] == 0x01u && data[i + 3u] == 0xB3u) { const uint32_t width = (static_cast(data[i + 4u]) << 4u) | (static_cast(data[i + 5u]) >> 4u); const uint32_t height = ((static_cast(data[i + 5u]) & 0x0Fu) << 8u) | static_cast(data[i + 6u]); const uint8_t frameRateCode = data[i + 7u] & 0x0Fu; if (width != 0u && height != 0u && width <= 4096u && height <= 4096u && mpegPictureIntervalQ32(frameRateCode) != 0u) { return i; } } } return kStartCodeNotFound; } size_t parsePesPayloadOffset(const uint8_t *packet, size_t packetSize) { if (!packet || packetSize <= 6u) { return packetSize; } size_t pos = 6u; if (packetSize >= 9u && (packet[pos] & 0xC0u) == 0x80u) { return std::min(packetSize, 9u + static_cast(packet[pos + 2u])); } while (pos < packetSize && packet[pos] == 0xFFu) { ++pos; } if (pos + 1u < packetSize && (packet[pos] & 0xC0u) == 0x40u) { pos += 2u; } if (pos >= packetSize) { return packetSize; } const uint8_t flags = packet[pos]; if ((flags & 0xF0u) == 0x20u) { pos += 5u; } else if ((flags & 0xF0u) == 0x30u) { pos += 10u; } else if (flags == 0x0Fu) { pos += 1u; } return std::min(packetSize, pos); } void flushDecoderIfEnded(MpegPlaybackState &playback) { if (playback.streamEnded && playback.decoder) { playback.decoder->flush(playback.decodedFrames); } } void feedElementaryStream(MpegPlaybackState &playback, const uint8_t *data, size_t size) { if (!data || size == 0) { return; } const uint32_t feedEsIdx = g_mpeg_stub_state.feedEsTraceCount++; if (feedEsIdx < 32u) { PS2_IF_AGRESSIVE_LOGS({ char hexBuf[16] = {}; for (size_t i = 0; i < std::min(4u, size); ++i) { ::snprintf(hexBuf + i * 2, 3, "%02x", data[i]); } std::cerr << "[MPEG:feedES] #" << feedEsIdx << " size=" << size << " first4=" << hexBuf << " decoderFailed=" << playback.decoderFailed << " waitSeq=" << playback.waitingForVideoSequenceHeader << std::endl; }); } playback.sawInput = true; if (playback.waitingForVideoSequenceHeader) { playback.videoSequenceSyncBuffer.insert( playback.videoSequenceSyncBuffer.end(), data, data + size); constexpr size_t kMaxVideoSequenceSyncBytes = 2u * 1024u * 1024u; if (playback.videoSequenceSyncBuffer.size() > kMaxVideoSequenceSyncBytes) { const size_t keepFrom = playback.videoSequenceSyncBuffer.size() - 3u; playback.videoSequenceSyncBuffer.erase( playback.videoSequenceSyncBuffer.begin(), playback.videoSequenceSyncBuffer.begin() + static_cast(keepFrom)); } const size_t sequenceHeader = findMpegSequenceHeader( playback.videoSequenceSyncBuffer.data(), playback.videoSequenceSyncBuffer.size()); if (sequenceHeader == kStartCodeNotFound) { return; } if (sequenceHeader != 0u) { playback.videoSequenceSyncBuffer.erase( playback.videoSequenceSyncBuffer.begin(), playback.videoSequenceSyncBuffer.begin() + static_cast(sequenceHeader)); } data = playback.videoSequenceSyncBuffer.data(); size = playback.videoSequenceSyncBuffer.size(); playback.pictureIntervalQ32 = mpegPictureIntervalQ32(data[7u] & 0x0Fu); playback.nextPictureTickQ32 = std::numeric_limits::max(); playback.waitingForVideoSequenceHeader = false; playback.decoderFailed = false; playback.decoder.reset(); playback.decodedFrames.clear(); } if (containsMpegSequenceEnd(data, size)) { playback.streamEnded = true; playback.cdStreamGeneration = g_mpeg_stub_state.cdStreamGeneration; } if (!playback.decoder) { playback.decoder = std::make_unique(); } if (!playback.decoder->feed(data, size, playback.decodedFrames)) { playback.decoder.reset(); playback.waitingForVideoSequenceHeader = true; playback.videoSequenceSyncBuffer.clear(); playback.decoderFailed = false; return; } playback.videoSequenceSyncBuffer.clear(); flushDecoderIfEnded(playback); } void erasePssPrefix(MpegPlaybackState &playback, size_t count) { std::vector &buffer = playback.pssBuffer; std::vector &guestAddrs = playback.pssGuestAddrs; const size_t clamped = std::min(count, buffer.size()); if (clamped == 0u) { return; } buffer.erase(buffer.begin(), buffer.begin() + static_cast(clamped)); if (guestAddrs.size() >= clamped) { guestAddrs.erase(guestAddrs.begin(), guestAddrs.begin() + static_cast(clamped)); } else { guestAddrs.clear(); } } std::vector matchingStreamCallbacks(uint32_t mpegAddr, uint32_t streamType) { std::vector out; auto it = g_mpeg_stub_state.callbacksByMpeg.find(mpegAddr); if (it == g_mpeg_stub_state.callbacksByMpeg.end()) { return out; } for (const MpegRegisteredCallback &callback : it->second) { if (callback.stream && callback.type == streamType) { out.push_back(callback); } } return out; } void queueStreamCallbackEvent(uint32_t mpegAddr, uint32_t streamType, uint32_t dataAddr, uint32_t len, std::vector &callbackEvents) { MpegStreamCallbackEvent event{}; event.mpegAddr = mpegAddr; event.streamType = streamType; event.dataAddr = dataAddr; event.len = len; event.callbacks = matchingStreamCallbacks(mpegAddr, streamType); if (!event.callbacks.empty()) { callbackEvents.push_back(std::move(event)); } } void processPssBuffer(uint32_t mpegAddr, MpegPlaybackState &playback, std::vector &callbackEvents, bool finalChunk = false) { std::vector &buffer = playback.pssBuffer; while (true) { if (playback.streamEnded) { erasePssPrefix(playback, buffer.size()); return; } const size_t start = findStartCode(buffer, 0u); if (start == kStartCodeNotFound) { if (finalChunk) { erasePssPrefix(playback, buffer.size()); return; } if (buffer.size() > 3u) { erasePssPrefix(playback, buffer.size() - 3u); } return; } if (start > 0u) { erasePssPrefix(playback, start); } if (buffer.size() < 4u) { return; } const uint8_t streamId = buffer[3u]; if (streamId == kMpegProgramEnd) { playback.streamEnded = true; playback.cdStreamGeneration = g_mpeg_stub_state.cdStreamGeneration; flushDecoderIfEnded(playback); erasePssPrefix(playback, buffer.size()); return; } if (streamId == kMpegPackHeader) { if (buffer.size() < 12u) { if (finalChunk) { erasePssPrefix(playback, buffer.size()); } return; } size_t packSize = 12u; if ((buffer[4u] & 0xC0u) == 0x40u) { if (buffer.size() < 14u) { if (finalChunk) { erasePssPrefix(playback, buffer.size()); } return; } packSize = 14u + static_cast(buffer[13u] & 0x07u); } if (buffer.size() < packSize) { if (finalChunk) { erasePssPrefix(playback, buffer.size()); } return; } erasePssPrefix(playback, packSize); continue; } if (buffer.size() < 6u) { if (finalChunk) { erasePssPrefix(playback, buffer.size()); } return; } const uint16_t packetLength = readBe16(buffer.data() + 4u); if (isLengthPrefixedHeader(streamId)) { const size_t packetEnd = 6u + static_cast(packetLength); if (buffer.size() < packetEnd) { if (finalChunk) { erasePssPrefix(playback, buffer.size()); } return; } erasePssPrefix(playback, packetEnd); continue; } size_t packetEnd = 0u; if (packetLength != 0u) { packetEnd = 6u + static_cast(packetLength); if (buffer.size() < packetEnd) { if (!finalChunk) { return; } packetEnd = buffer.size(); } } else { const size_t next = findStartCode(buffer, 6u); if (next == kStartCodeNotFound) { if (!finalChunk) { return; } packetEnd = buffer.size(); } else { packetEnd = next; } } if (isVideoStreamId(streamId)) { const size_t payloadStart = parsePesPayloadOffset(buffer.data(), packetEnd); if (payloadStart < packetEnd) { if (payloadStart < playback.pssGuestAddrs.size()) { queueStreamCallbackEvent( mpegAddr, kMpegStrM2V, playback.pssGuestAddrs[payloadStart], static_cast(packetEnd - payloadStart), callbackEvents); } feedElementaryStream( playback, buffer.data() + payloadStart, packetEnd - payloadStart); } } else if (isAudioStreamId(streamId)) { const size_t payloadStart = parsePesPayloadOffset(buffer.data(), packetEnd); if (payloadStart < packetEnd && payloadStart < playback.pssGuestAddrs.size()) { queueStreamCallbackEvent( mpegAddr, kMpegStrPCM, playback.pssGuestAddrs[payloadStart], static_cast(packetEnd - payloadStart), callbackEvents); queueStreamCallbackEvent( mpegAddr, kMpegStrADPCM, playback.pssGuestAddrs[payloadStart], static_cast(packetEnd - payloadStart), callbackEvents); } } erasePssPrefix(playback, packetEnd); } } void finishPlaybackStream(uint32_t mpegAddr, MpegPlaybackState &playback) { std::vector ignoredCallbacks; processPssBuffer(mpegAddr, playback, ignoredCallbacks, true); playback.streamEnded = true; playback.cdStreamGeneration = g_mpeg_stub_state.cdStreamGeneration; flushDecoderIfEnded(playback); } void finalizeCdStreamEofUnlocked(std::vector &completedMpegIds, bool &changed) { g_mpeg_stub_state.cdStreamEofPending = false; g_mpeg_stub_state.currentCdStreamEofSeen = true; for (auto &[mpegAddr, playback] : g_mpeg_stub_state.playbackByMpeg) { completedMpegIds.push_back(mpegAddr); if (!playback.sawInput || playback.streamEnded) { continue; } finishPlaybackStream(mpegAddr, playback); changed = true; } } void recordCdStreamBytesDemuxedUnlocked( size_t consumed, std::vector &completedMpegIds, bool &changed) { g_mpeg_stub_state.cdStreamBytesDemuxed += consumed; if (g_mpeg_stub_state.cdStreamEofPending && g_mpeg_stub_state.cdStreamBytesDemuxed >= g_mpeg_stub_state.cdStreamBytesProduced) { finalizeCdStreamEofUnlocked(completedMpegIds, changed); } } void appendPssBytes(uint32_t mpegAddr, MpegPlaybackState &playback, const uint8_t *data, size_t size, uint32_t guestAddr, std::vector &callbackEvents) { if (!data || size == 0) { return; } if (playback.sawInput && playback.cdStreamGeneration != g_mpeg_stub_state.cdStreamGeneration) { playback = makeFreshPlaybackState(); } if (playback.streamEnded) { if (playback.cdStreamGeneration == g_mpeg_stub_state.cdStreamGeneration) { playback.sawInput = true; return; } playback = makeFreshPlaybackState(); } if (!playback.sawInput) { playback.cdStreamGeneration = g_mpeg_stub_state.cdStreamGeneration; } 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) { playback.pssGuestAddrs.push_back(guestAddr + static_cast(i)); } processPssBuffer(mpegAddr, playback, callbackEvents); } size_t appendGuestBytes(uint32_t mpegAddr, MpegPlaybackState &playback, const uint8_t *rdram, uint32_t addr, size_t size, std::vector &callbackEvents) { size_t copied = 0u; while (copied < size) { const uint32_t curAddr = addr + static_cast(copied); const uint32_t offset = curAddr & PS2_RAM_MASK; size_t chunk = std::min(size - copied, PS2_RAM_SIZE - offset); if (chunk == 0u) { break; } const uint8_t *src = getConstMemPtr(rdram, curAddr); if (!src) { break; } appendPssBytes( mpegAddr, playback, src, chunk, curAddr, callbackEvents); copied += chunk; } return copied; } size_t appendGuestRingBytes(uint32_t mpegAddr, MpegPlaybackState &playback, const uint8_t *rdram, uint32_t dataAddr, uint32_t byteCount, uint32_t ringBaseAddr, uint32_t ringSize, std::vector &callbackEvents) { if (byteCount == 0u) { return 0u; } const uint32_t base = ringBaseAddr & PS2_RAM_MASK; const uint32_t data = dataAddr & PS2_RAM_MASK; if (ringBaseAddr != 0u && ringSize != 0u && ringSize <= PS2_RAM_SIZE) { const uint32_t ringOffset = (data - base) & PS2_RAM_MASK; if (ringOffset < ringSize) { const uint32_t first = std::min(byteCount, ringSize - ringOffset); size_t copied = appendGuestBytes( mpegAddr, playback, rdram, dataAddr, first, callbackEvents); if (copied < first) { return copied; } const uint32_t remaining = byteCount - first; if (remaining != 0u) { copied += appendGuestBytes( mpegAddr, playback, rdram, ringBaseAddr, remaining, callbackEvents); } return copied; } } return appendGuestBytes(mpegAddr, playback, rdram, dataAddr, byteCount, callbackEvents); } bool writeMpegCallbackData(uint8_t *rdram, uint32_t addr, const MpegStreamCallbackEvent &event) { if (!rdram || addr == 0u) { return false; } uint8_t *data = getMemPtr(rdram, addr); if (!data) { return false; } std::memset(data, 0, kMpegCallbackDataSize); *reinterpret_cast(data + 0x00u) = event.streamType; *reinterpret_cast(data + 0x08u) = event.dataAddr; *reinterpret_cast(data + 0x0Cu) = event.len; *reinterpret_cast(data + 0x10u) = event.pts; *reinterpret_cast(data + 0x18u) = event.dts; return true; } void dispatchGuestStreamCallback(uint8_t *rdram, R5900Context *callerCtx, PS2Runtime *runtime, const MpegStreamCallbackEvent &event, const MpegRegisteredCallback &callback) { if (!rdram || !callerCtx || !runtime || callback.func == 0u || !runtime->hasFunction(callback.func)) { return; } const uint32_t cbDataAddr = runtime->guestMalloc(kMpegCallbackDataSize, 16u); if (cbDataAddr == 0u) { return; } if (!writeMpegCallbackData(rdram, cbDataAddr, event)) { runtime->guestFree(cbDataAddr); return; } R5900Context callbackCtx = *callerCtx; SET_GPR_U32(&callbackCtx, 4, event.mpegAddr); SET_GPR_U32(&callbackCtx, 5, cbDataAddr); SET_GPR_U32(&callbackCtx, 6, callback.data); SET_GPR_U32(&callbackCtx, 7, 0u); SET_GPR_U32(&callbackCtx, 29, 0u); SET_GPR_U32(&callbackCtx, 31, 0u); callbackCtx.pc = callback.func; GuestInvocation invocation{}; invocation.kind = GuestInvocationKind::RpcCallback; invocation.context = callbackCtx; invocation.onComplete = [runtime, cbDataAddr](const R5900Context &, R5900Context &) { runtime->guestFree(cbDataAddr); }; runtime->eeScheduler().queueInvocation(std::move(invocation)); } void dispatchStreamCallbacks(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, const std::vector &events) { if (events.empty()) { return; } for (const MpegStreamCallbackEvent &event : events) { for (const MpegRegisteredCallback &callback : event.callbacks) { dispatchGuestStreamCallback(rdram, ctx, runtime, event, callback); } } } void dispatchStreamCallbacksUnlocked(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, const std::vector &events) { if (events.empty()) { return; } dispatchStreamCallbacks(rdram, ctx, runtime, events); } void writeBlankMpegFrame(uint8_t *rdram, uint32_t destAddr, uint32_t width, uint32_t height) { if (!rdram || destAddr == 0u) { return; } const uint32_t outWidth = align16(width == 0u ? kStubMovieWidth : width); const uint32_t outHeight = align16(height == 0u ? kStubMovieHeight : height); const uint32_t macroblockColumns = outWidth / 16u; for (uint32_t mbx = 0u; mbx < macroblockColumns; ++mbx) { const size_t stripOffset = static_cast(mbx) * static_cast(outHeight) * 16u * 4u; for (uint32_t y = 0u; y < outHeight; ++y) { uint8_t *dst = getMemPtr( rdram, destAddr + static_cast(stripOffset + static_cast(y) * 16u * 4u)); if (!dst) { continue; } for (uint32_t x = 0u; x < 16u; ++x) { dst[x * 4u + 0u] = 0u; dst[x * 4u + 1u] = 0u; dst[x * 4u + 2u] = 0u; dst[x * 4u + 3u] = 0x80u; } } } } void writeDecodedFrameToGuest(uint8_t *rdram, uint32_t destAddr, const MpegDecodedFrame &frame) { if (!rdram || destAddr == 0u || frame.rgba.empty() || frame.width <= 0 || frame.height <= 0) { return; } const uint32_t width = static_cast(frame.width); const uint32_t height = static_cast(frame.height); const uint32_t outWidth = align16(width); const uint32_t outHeight = align16(height); const uint32_t macroblockColumns = outWidth / 16u; for (uint32_t mbx = 0u; mbx < macroblockColumns; ++mbx) { const size_t stripOffset = static_cast(mbx) * static_cast(outHeight) * 16u * 4u; for (uint32_t y = 0u; y < outHeight; ++y) { uint8_t *dst = getMemPtr( rdram, destAddr + static_cast(stripOffset + static_cast(y) * 16u * 4u)); if (!dst) { continue; } for (uint32_t x = 0u; x < 16u; ++x) { const uint32_t srcX = mbx * 16u + x; const uint8_t *src = nullptr; if (srcX < width && y < height) { src = frame.rgba.data() + (static_cast(y) * static_cast(width) + srcX) * 4u; } if (src) { dst[x * 4u + 0u] = src[0u]; dst[x * 4u + 1u] = src[1u]; dst[x * 4u + 2u] = src[2u]; dst[x * 4u + 3u] = 0x80u; } else { dst[x * 4u + 0u] = 0u; dst[x * 4u + 1u] = 0u; dst[x * 4u + 2u] = 0u; dst[x * 4u + 3u] = 0x80u; } } } } } void resetMpegStubStateUnlocked() { g_mpeg_stub_state.initialized = false; g_mpeg_stub_state.nextCallbackHandle = 1u; g_mpeg_stub_state.cdStreamGeneration = 0u; g_mpeg_stub_state.cdStreamBytesProduced = 0u; g_mpeg_stub_state.cdStreamBytesDemuxed = 0u; g_mpeg_stub_state.cdStreamEofPending = false; g_mpeg_stub_state.currentCdStreamEofSeen = false; g_mpeg_stub_state.feedEsTraceCount = 0u; g_mpeg_stub_state.demuxPssTraceCount = 0u; g_mpeg_stub_state.demuxRingTraceCount = 0u; g_mpeg_stub_state.getPictureWaitTraceCount = 0u; g_mpeg_stub_state.pictureTraceCount = 0u; g_mpeg_stub_state.isEndTraceCount = 0u; g_mpeg_stub_state.callbacksByMpeg.clear(); g_mpeg_stub_state.playbackByMpeg.clear(); } } void resetMpegStubState() { std::lock_guard lock(g_mpeg_stub_mutex); resetMpegStubStateUnlocked(); } void enqueueMpegDecodedFrameForTesting(uint32_t mpegAddr) { constexpr int kTestFrameWidth = 16; constexpr int kTestFrameHeight = 16; MpegDecodedFrame frame; frame.width = kTestFrameWidth; frame.height = kTestFrameHeight; frame.rgba.resize(static_cast(kTestFrameWidth * kTestFrameHeight * 4), 0x80u); std::lock_guard lock(g_mpeg_stub_mutex); MpegPlaybackState &playback = getPlaybackState(mpegAddr); playback.sawInput = true; playback.decodedFrames.push_back(std::move(frame)); } void notifyMpegCdStreamStart(PS2Runtime *runtime) { (void)runtime; std::lock_guard lock(g_mpeg_stub_mutex); ++g_mpeg_stub_state.cdStreamGeneration; g_mpeg_stub_state.cdStreamBytesProduced = 0u; g_mpeg_stub_state.cdStreamBytesDemuxed = 0u; g_mpeg_stub_state.cdStreamEofPending = false; g_mpeg_stub_state.currentCdStreamEofSeen = false; g_mpeg_stub_state.feedEsTraceCount = 0u; g_mpeg_stub_state.demuxPssTraceCount = 0u; g_mpeg_stub_state.demuxRingTraceCount = 0u; g_mpeg_stub_state.getPictureWaitTraceCount = 0u; g_mpeg_stub_state.pictureTraceCount = 0u; g_mpeg_stub_state.isEndTraceCount = 0u; for (auto &[mpegAddr, playback] : g_mpeg_stub_state.playbackByMpeg) { playback = makeFreshPlaybackStatePreservingConfig(playback); } PS2_IF_AGRESSIVE_LOGS({ std::cerr << "[MPEG:CdStreamStart] generation=" << g_mpeg_stub_state.cdStreamGeneration << " reopened=" << g_mpeg_stub_state.playbackByMpeg.size() << std::endl; }); } void notifyMpegCdStreamDataProduced(uint32_t byteCount, bool endOfStream) { std::lock_guard lock(g_mpeg_stub_mutex); g_mpeg_stub_state.cdStreamBytesProduced += byteCount; if (endOfStream) { g_mpeg_stub_state.cdStreamEofPending = true; } } void notifyMpegCdStreamEof(PS2Runtime *runtime) { std::vector completedMpegIds; bool changed = false; { std::lock_guard lock(g_mpeg_stub_mutex); finalizeCdStreamEofUnlocked(completedMpegIds, changed); } if (changed) { static uint32_t s_eofLogCount = 0u; if (s_eofLogCount < 8u) { PS2_IF_AGRESSIVE_LOGS({ std::cerr << "[MPEG:CdStreamEof] finalized active MPEG playback" << std::endl; }); ++s_eofLogCount; } } if (runtime) { for (const uint32_t mpegAddr : completedMpegIds) { runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK); } } } void sceMpegFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; const uint32_t mpegAddr = getRegU32(ctx, 4); { std::lock_guard lock(g_mpeg_stub_mutex); MpegPlaybackState &playback = getPlaybackState(mpegAddr); if (playback.decoder) { playback.decoder->flush(playback.decodedFrames); } } runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK); setReturnS32(ctx, 0); } void sceMpegAddBs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { const uint32_t mpegAddr = getRegU32(ctx, 4); const uint32_t dataAddr = getRegU32(ctx, 5); const uint32_t byteCount = getRegU32(ctx, 6); size_t copied = 0u; { std::lock_guard lock(g_mpeg_stub_mutex); MpegPlaybackState &playback = getPlaybackState(mpegAddr); while (copied < byteCount) { const uint32_t curAddr = dataAddr + static_cast(copied); const uint32_t offset = curAddr & PS2_RAM_MASK; const size_t chunk = std::min(static_cast(byteCount) - copied, PS2_RAM_SIZE - offset); const uint8_t *src = getConstMemPtr(rdram, curAddr); if (!src || chunk == 0u) { break; } feedElementaryStream(playback, src, chunk); copied += chunk; } } runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK); setReturnS32(ctx, static_cast(copied)); } void sceMpegAddCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; (void)runtime; const uint32_t mpegAddr = getRegU32(ctx, 4); const uint32_t callbackType = getRegU32(ctx, 5); const uint32_t callbackFunc = getRegU32(ctx, 6); const uint32_t callbackData = getRegU32(ctx, 7); std::lock_guard lock(g_mpeg_stub_mutex); g_mpeg_stub_state.initialized = true; (void)getPlaybackState(mpegAddr); const uint32_t handle = g_mpeg_stub_state.nextCallbackHandle++; g_mpeg_stub_state.callbacksByMpeg[mpegAddr].push_back( MpegRegisteredCallback{callbackType, 0u, callbackFunc, callbackData, handle, false}); setReturnU32(ctx, handle); } void sceMpegAddStrCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)runtime; const uint32_t mpegAddr = getRegU32(ctx, 4); const uint32_t streamType = getRegU32(ctx, 5); const uint32_t streamId = getRegU32(ctx, 6); const uint32_t callbackFunc = getRegU32(ctx, 7); const uint32_t callbackData = readAbiArg4(rdram, ctx); std::lock_guard lock(g_mpeg_stub_mutex); g_mpeg_stub_state.initialized = true; (void)getPlaybackState(mpegAddr); const uint32_t handle = g_mpeg_stub_state.nextCallbackHandle++; g_mpeg_stub_state.callbacksByMpeg[mpegAddr].push_back( MpegRegisteredCallback{streamType, streamId, callbackFunc, callbackData, handle, true}); setReturnU32(ctx, 0u); } void sceMpegClearRefBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)ctx; (void)runtime; static const uint32_t kRefGlobalAddrs[] = { 0x171800u, 0x17180Cu, 0x171818u, 0x171804u, 0x171810u, 0x17181Cu}; for (uint32_t addr : kRefGlobalAddrs) { uint8_t *p = getMemPtr(rdram, addr); if (!p) continue; uint32_t ptr = *reinterpret_cast(p); if (ptr != 0u) { uint8_t *q = getMemPtr(rdram, ptr + 0x28u); if (q) *reinterpret_cast(q) = 0u; } } setReturnU32(ctx, 1u); } static void mpegGuestWrite32(uint8_t *rdram, uint32_t addr, uint32_t value) { if (uint8_t *p = getMemPtr(rdram, addr)) *reinterpret_cast(p) = value; } static void mpegGuestWrite64(uint8_t *rdram, uint32_t addr, uint64_t value) { if (uint8_t *p = getMemPtr(rdram, addr)) { *reinterpret_cast(p) = static_cast(value); *reinterpret_cast(p + 4) = static_cast(value >> 32); } } void sceMpegCreate(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { const uint32_t param_1 = getRegU32(ctx, 4); // a0 const uint32_t param_2 = getRegU32(ctx, 5); // a1 const uint32_t param_3 = getRegU32(ctx, 6); // a2 const uint32_t uVar3 = (param_2 + 3u) & 0xFFFFFFFCu; const int32_t iVar2_signed = static_cast(param_3) - static_cast(uVar3 - param_2); if (iVar2_signed <= 0x117) { setReturnU32(ctx, 0u); return; } { std::lock_guard lock(g_mpeg_stub_mutex); getPlaybackState(param_1) = makeFreshPlaybackState(); } const uint32_t puVar4 = uVar3 + 0x108u; const uint32_t innerSize = static_cast(iVar2_signed) - 0x118u; mpegGuestWrite32(rdram, param_1 + 0x40, uVar3); const uint32_t a1_init = uVar3 + 0x118u; mpegGuestWrite32(rdram, puVar4 + 0x0, a1_init); mpegGuestWrite32(rdram, puVar4 + 0x4, innerSize); mpegGuestWrite32(rdram, puVar4 + 0x8, a1_init); mpegGuestWrite32(rdram, puVar4 + 0xC, a1_init); const uint32_t allocResult = runtime ? runtime->guestMalloc(0x600, 8u) : (uVar3 + 0x200u); mpegGuestWrite32(rdram, uVar3 + 0x44, allocResult); // param_1[0..2] = 0; param_1[4..0xe] = 0xffffffff/0 as per decompilation mpegGuestWrite32(rdram, param_1 + 0x00, 0); mpegGuestWrite32(rdram, param_1 + 0x04, 0); mpegGuestWrite32(rdram, param_1 + 0x08, 0); mpegGuestWrite64(rdram, param_1 + 0x10, 0xFFFFFFFFFFFFFFFFULL); mpegGuestWrite64(rdram, param_1 + 0x18, 0xFFFFFFFFFFFFFFFFULL); mpegGuestWrite64(rdram, param_1 + 0x20, 0); mpegGuestWrite64(rdram, param_1 + 0x28, 0xFFFFFFFFFFFFFFFFULL); mpegGuestWrite64(rdram, param_1 + 0x30, 0xFFFFFFFFFFFFFFFFULL); mpegGuestWrite64(rdram, param_1 + 0x38, 0); static const unsigned s_zeroOffsets[] = { 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0, 0xE4, 0xE8, 0xF8, 0x0C, 0x14, 0x2C, 0x34, 0x3C, 0x48, 0xFC, 0x100, 0x104, 0x70, 0x90, 0xAC}; for (unsigned off : s_zeroOffsets) mpegGuestWrite32(rdram, uVar3 + off, 0u); mpegGuestWrite64(rdram, uVar3 + 0x78, 0); mpegGuestWrite64(rdram, uVar3 + 0x88, 0); mpegGuestWrite64(rdram, uVar3 + 0xF0, 0xFFFFFFFFFFFFFFFFULL); mpegGuestWrite32(rdram, uVar3 + 0x1C, 0x1209F8u); mpegGuestWrite32(rdram, uVar3 + 0x24, 0x120A08u); mpegGuestWrite32(rdram, uVar3 + 0xB0, 1u); mpegGuestWrite32(rdram, uVar3 + 0x9C, 0xFFFFFFFFu); mpegGuestWrite32(rdram, uVar3 + 0x80, 0xFFFFFFFFu); mpegGuestWrite32(rdram, uVar3 + 0x94, 0xFFFFFFFFu); mpegGuestWrite32(rdram, uVar3 + 0x98, 0xFFFFFFFFu); mpegGuestWrite32(rdram, 0x1717BCu, param_1); static const uint32_t s_refValues[] = { 0x171A50u, 0x171C58u, 0x171CC0u, 0x171D28u, 0x171D90u, 0x171AB8u, 0x171B20u, 0x171B88u, 0x171BF0u}; for (unsigned i = 0; i < 9u; ++i) mpegGuestWrite32(rdram, 0x171800u + i * 4u, s_refValues[i]); uint32_t setDynamicRet = a1_init; if (uint8_t *p = getMemPtr(rdram, puVar4 + 8)) setDynamicRet = *reinterpret_cast(p); mpegGuestWrite32(rdram, puVar4 + 12, setDynamicRet); setReturnU32(ctx, setDynamicRet); } void sceMpegDelete(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; const uint32_t mpegAddr = getRegU32(ctx, 4); { std::lock_guard lock(g_mpeg_stub_mutex); g_mpeg_stub_state.callbacksByMpeg.erase(mpegAddr); g_mpeg_stub_state.playbackByMpeg.erase(mpegAddr); } runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_WAIT_DELETE); setReturnU32(ctx, 0u); } void sceMpegDemuxPss(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { const uint32_t mpegAddr = getRegU32(ctx, 4); const uint32_t dataAddr = getRegU32(ctx, 5); const uint32_t byteCount = getRegU32(ctx, 6); std::vector callbackEvents; std::vector completedMpegIds; size_t consumed = 0u; size_t decodedCount = 0u; uint32_t traceIdx = 0u; bool eofChanged = false; { std::lock_guard lock(g_mpeg_stub_mutex); MpegPlaybackState &playback = getPlaybackState(mpegAddr); consumed = appendGuestBytes(mpegAddr, playback, rdram, dataAddr, byteCount, callbackEvents); recordCdStreamBytesDemuxedUnlocked(consumed, completedMpegIds, eofChanged); decodedCount = playback.decodedFrames.size(); traceIdx = g_mpeg_stub_state.demuxPssTraceCount++; } runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK); for (const uint32_t completedMpegId : completedMpegIds) { if (completedMpegId != mpegAddr) { runtime->eeScheduler().completeExternalWait( kMpegPictureWaitType, completedMpegId, KE_OK); } } if (traceIdx < 32u) { PS2_IF_AGRESSIVE_LOGS({ std::cerr << "[MPEG:DemuxPss] mpeg=0x" << std::hex << mpegAddr << " data=0x" << dataAddr << std::dec << " bytes=" << byteCount << " consumed=" << consumed << " decoded=" << decodedCount << " callbacks=" << callbackEvents.size() << std::endl; }); } dispatchStreamCallbacksUnlocked(rdram, ctx, runtime, callbackEvents); setReturnS32(ctx, static_cast(consumed)); } void sceMpegDemuxPssRing(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { static std::atomic s_demuxRingEntryCount{0u}; const uint32_t entryIdx = s_demuxRingEntryCount.fetch_add(1u, std::memory_order_relaxed); if (entryIdx < 4u) { PS2_IF_AGRESSIVE_LOGS({ std::cerr << "[MPEG:DemuxPssRing:ENTER] call #" << entryIdx << " pc=0x" << std::hex << ctx->pc << " ra=0x" << getRegU32(ctx, 31) << std::dec << std::endl; }); } const uint32_t mpegAddr = getRegU32(ctx, 4); const uint32_t dataAddr = getRegU32(ctx, 5); const uint32_t availableBytes = getRegU32(ctx, 6); const uint32_t ringBaseAddr = getRegU32(ctx, 7); const uint32_t ringSize = readAbiArg4(rdram, ctx); std::vector callbackEvents; std::vector completedMpegIds; size_t consumed = 0u; size_t decodedCount = 0u; uint32_t traceIdx = 0u; bool eofChanged = false; { std::lock_guard lock(g_mpeg_stub_mutex); MpegPlaybackState &playback = getPlaybackState(mpegAddr); consumed = appendGuestRingBytes( mpegAddr, playback, rdram, dataAddr, availableBytes, ringBaseAddr, ringSize, callbackEvents); recordCdStreamBytesDemuxedUnlocked(consumed, completedMpegIds, eofChanged); decodedCount = playback.decodedFrames.size(); traceIdx = g_mpeg_stub_state.demuxRingTraceCount++; } runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK); for (const uint32_t completedMpegId : completedMpegIds) { if (completedMpegId != mpegAddr) { runtime->eeScheduler().completeExternalWait( kMpegPictureWaitType, completedMpegId, KE_OK); } } if (traceIdx < 32u) { PS2_IF_AGRESSIVE_LOGS({ std::cerr << "[MPEG:DemuxPssRing] mpeg=0x" << std::hex << mpegAddr << " data=0x" << dataAddr << " ring=0x" << ringBaseAddr << std::dec << " avail=" << availableBytes << " consumed=" << consumed << " decoded=" << decodedCount << " callbacks=" << callbackEvents.size() << std::endl; }); } dispatchStreamCallbacksUnlocked(rdram, ctx, runtime, callbackEvents); setReturnS32(ctx, static_cast(consumed)); } void sceMpegDispCenterOffX(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; (void)runtime; setReturnS32(ctx, 0); } void sceMpegDispCenterOffY(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; (void)runtime; setReturnS32(ctx, 0); } void sceMpegDispHeight(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; (void)runtime; const uint32_t mpegAddr = getRegU32(ctx, 4); std::lock_guard lock(g_mpeg_stub_mutex); setReturnU32(ctx, getPlaybackState(mpegAddr).height); } void sceMpegDispWidth(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; (void)runtime; const uint32_t mpegAddr = getRegU32(ctx, 4); std::lock_guard lock(g_mpeg_stub_mutex); setReturnU32(ctx, getPlaybackState(mpegAddr).width); } void sceMpegGetDecodeMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; (void)runtime; const uint32_t mpegAddr = getRegU32(ctx, 4); std::lock_guard lock(g_mpeg_stub_mutex); setReturnU32(ctx, getPlaybackState(mpegAddr).decodeMode); } void sceMpegGetPicture(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { const uint32_t mpegAddr = getRegU32(ctx, 4); const uint32_t imageAddr = getRegU32(ctx, 5); uint32_t width = kStubMovieWidth; uint32_t height = kStubMovieHeight; uint32_t frameCount = 0u; bool haveFrame = false; MpegDecodedFrame frame; { std::unique_lock lock(g_mpeg_stub_mutex); MpegPlaybackState &playback = getPlaybackState(mpegAddr); if (playback.decodedFrames.empty() && !g_mpeg_stub_state.currentCdStreamEofSeen && !playback.streamEnded && !playback.decoderFailed) { if (g_mpeg_stub_state.getPictureWaitTraceCount < 32u) { PS2_IF_AGRESSIVE_LOGS({ std::cerr << "[MPEG:GetPicture] waiting for frames, mpeg=0x" << std::hex << mpegAddr << std::dec << " ended=" << playback.streamEnded << " failed=" << playback.decoderFailed << " sawInput=" << playback.sawInput << std::endl; }); ++g_mpeg_stub_state.getPictureWaitTraceCount; } lock.unlock(); runtime->eeScheduler().waitExternal( EeWaitReason::Mpeg, kMpegPictureWaitType, mpegAddr, [rdram, runtime](R5900Context &resumeContext) { if (static_cast(getRegU32(&resumeContext, 2)) < 0) { return; } sceMpegGetPicture(rdram, &resumeContext, runtime); }); } if (!playback.decodedFrames.empty()) { if (playback.pictureIntervalQ32 != 0u) { const uint64_t currentTick = runtime->eeScheduler().currentVSyncTick(); const uint64_t currentTickQ32 = currentTick << 32u; if (playback.nextPictureTickQ32 == std::numeric_limits::max()) { playback.nextPictureTickQ32 = currentTickQ32; } if (currentTickQ32 < playback.nextPictureTickQ32) { const uint64_t eligibleTick = (playback.nextPictureTickQ32 + kPictureClockOne - 1u) >> 32u; lock.unlock(); runtime->eeScheduler().waitVSync( eligibleTick - 1u, -1, [rdram, runtime](R5900Context &resumeContext) { if (static_cast(getRegU32(&resumeContext, 2)) < 0) { return; } sceMpegGetPicture(rdram, &resumeContext, runtime); }); } // If decoding fell more than one frame behind, resume from the // current field instead of releasing a burst of stale pictures. if (currentTickQ32 - playback.nextPictureTickQ32 >= playback.pictureIntervalQ32) { playback.nextPictureTickQ32 = currentTickQ32; } playback.nextPictureTickQ32 += playback.pictureIntervalQ32; } frame = std::move(playback.decodedFrames.front()); playback.decodedFrames.pop_front(); playback.width = static_cast(frame.width); playback.height = static_cast(frame.height); width = playback.width; height = playback.height; frameCount = playback.picturesServed; playback.picturesServed += 1u; haveFrame = true; if (g_mpeg_stub_state.pictureTraceCount < 32u) { PS2_IF_AGRESSIVE_LOGS({ std::cerr << "[MPEG:GetPicture:FRAME] mpeg=0x" << std::hex << mpegAddr << std::dec << " generation=" << g_mpeg_stub_state.cdStreamGeneration << " frame=" << frameCount << " queued=" << playback.decodedFrames.size() << " size=" << width << "x" << height << std::endl; }); ++g_mpeg_stub_state.pictureTraceCount; } } else { width = playback.width; height = playback.height; frameCount = playback.picturesServed; } } mpegGuestWrite32(rdram, mpegAddr + 0x00u, width); mpegGuestWrite32(rdram, mpegAddr + 0x04u, height); mpegGuestWrite32(rdram, mpegAddr + 0x08u, frameCount); if (uint8_t *base = getMemPtr(rdram, mpegAddr)) { const uint32_t iVar1 = *reinterpret_cast(base + 0x40); if (uint8_t *inner = getMemPtr(rdram, iVar1)) { *reinterpret_cast(inner + 0xb0) = 1; *reinterpret_cast(inner + 0xd8) = (getRegU32(ctx, 5) & 0x0FFFFFFFu) | 0x20000000u; *reinterpret_cast(inner + 0xe4) = getRegU32(ctx, 6); *reinterpret_cast(inner + 0xdc) = 0; *reinterpret_cast(inner + 0xe0) = 0; } } if (haveFrame) { writeDecodedFrameToGuest(rdram, imageAddr, frame); } else if (frameCount == 0u) { writeBlankMpegFrame(rdram, imageAddr, width, height); } setReturnS32(ctx, 0); } void sceMpegGetPictureRAW8(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { TODO_NAMED("sceMpegGetPictureRAW8", rdram, ctx, runtime); } void sceMpegGetPictureRAW8xy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { TODO_NAMED("sceMpegGetPictureRAW8xy", rdram, ctx, runtime); } void sceMpegInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; std::lock_guard lock(g_mpeg_stub_mutex); const uint64_t cdStreamGeneration = g_mpeg_stub_state.cdStreamGeneration; const uint64_t cdStreamBytesProduced = g_mpeg_stub_state.cdStreamBytesProduced; const uint64_t cdStreamBytesDemuxed = g_mpeg_stub_state.cdStreamBytesDemuxed; const bool cdStreamEofPending = g_mpeg_stub_state.cdStreamEofPending; const bool currentCdStreamEofSeen = g_mpeg_stub_state.currentCdStreamEofSeen; resetMpegStubStateUnlocked(); g_mpeg_stub_state.initialized = true; g_mpeg_stub_state.cdStreamGeneration = cdStreamGeneration; g_mpeg_stub_state.cdStreamBytesProduced = cdStreamBytesProduced; g_mpeg_stub_state.cdStreamBytesDemuxed = cdStreamBytesDemuxed; g_mpeg_stub_state.cdStreamEofPending = cdStreamEofPending; g_mpeg_stub_state.currentCdStreamEofSeen = currentCdStreamEofSeen; setReturnU32(ctx, 0u); } void sceMpegIsEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; const uint32_t mpegAddr = getRegU32(ctx, 4); std::lock_guard lock(g_mpeg_stub_mutex); g_mpeg_stub_state.initialized = true; MpegPlaybackState &playback = getPlaybackState(mpegAddr); const bool ended = playback.streamEnded || (playback.decoderFailed && playback.sawInput); if (g_mpeg_stub_state.isEndTraceCount < 16u) { PS2_IF_AGRESSIVE_LOGS({ std::cerr << "[MPEG:IsEnd] mpeg=0x" << std::hex << mpegAddr << std::dec << " ended=" << ended << " frames=" << playback.decodedFrames.size() << " sawInput=" << playback.sawInput << std::endl; }); ++g_mpeg_stub_state.isEndTraceCount; } setReturnS32(ctx, (ended && playback.decodedFrames.empty()) ? 1 : 0); } void sceMpegIsRefBuffEmpty(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; (void)runtime; const uint32_t mpegAddr = getRegU32(ctx, 4); std::lock_guard lock(g_mpeg_stub_mutex); const MpegPlaybackState &playback = getPlaybackState(mpegAddr); setReturnS32(ctx, playback.decodedFrames.empty() ? 1 : 0); } void sceMpegReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)runtime; const uint32_t param_1 = getRegU32(ctx, 4); { std::lock_guard lock(g_mpeg_stub_mutex); MpegPlaybackState &playback = getPlaybackState(param_1); MpegPlaybackState resetState = makeFreshPlaybackStatePreservingConfig(playback); if (playback.streamEnded || playback.decoderFailed) { resetState.sawInput = true; resetState.streamEnded = true; resetState.cdStreamGeneration = playback.cdStreamGeneration; } playback = std::move(resetState); } uint8_t *base = getMemPtr(rdram, param_1); if (!base) { return; } uint32_t inner = *reinterpret_cast(base + 0x40); if (inner == 0u) return; mpegGuestWrite32(rdram, param_1 + 0x00u, 0u); mpegGuestWrite32(rdram, param_1 + 0x04u, 0u); mpegGuestWrite32(rdram, param_1 + 0x08u, 0u); mpegGuestWrite32(rdram, inner + 0x00, 0u); mpegGuestWrite32(rdram, inner + 0x04, 0u); mpegGuestWrite32(rdram, inner + 0x08, 0u); mpegGuestWrite32(rdram, param_1 + 0x08, 0u); mpegGuestWrite32(rdram, inner + 0x80, 0xFFFFFFFFu); mpegGuestWrite32(rdram, inner + 0xAC, 0u); mpegGuestWrite32(rdram, 0x171904u, 0u); } void sceMpegResetDefaultPtsGap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; (void)runtime; setReturnS32(ctx, 0); } void sceMpegSetDecodeMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; (void)runtime; const uint32_t mpegAddr = getRegU32(ctx, 4); const uint32_t mode = getRegU32(ctx, 5); std::lock_guard lock(g_mpeg_stub_mutex); getPlaybackState(mpegAddr).decodeMode = mode; setReturnS32(ctx, 0); } void sceMpegSetDefaultPtsGap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; (void)runtime; setReturnS32(ctx, 0); } void sceMpegSetImageBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { (void)rdram; (void)runtime; const uint32_t mpegAddr = getRegU32(ctx, 4); const uint32_t imageBufferAddr = getRegU32(ctx, 5); std::lock_guard lock(g_mpeg_stub_mutex); getPlaybackState(mpegAddr).imageBufferAddr = imageBufferAddr; setReturnS32(ctx, 0); } }