mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-10-03 02:56:41 -04:00
2218 lines
80 KiB
C++
2218 lines
80 KiB
C++
#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 <libavcodec/avcodec.h>
|
|
#include <libavutil/error.h>
|
|
#include <libavutil/log.h>
|
|
#include <libswscale/swscale.h>
|
|
}
|
|
#endif
|
|
|
|
#include <deque>
|
|
#include <memory>
|
|
|
|
#include "Syscalls/Helpers/State.h"
|
|
|
|
namespace ps2_stubs
|
|
{
|
|
namespace
|
|
{
|
|
struct MpegDecodedFrame
|
|
{
|
|
int width = 0;
|
|
int height = 0;
|
|
std::vector<uint8_t> rgba;
|
|
};
|
|
|
|
#if PS2X_HAS_FFMPEG
|
|
std::string ffmpegErrorString(int err)
|
|
{
|
|
std::array<char, AV_ERROR_MAX_STRING_SIZE> 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<MpegDecodedFrame> &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<int>(std::min<size_t>(
|
|
remaining, static_cast<size_t>(std::numeric_limits<int>::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<size_t>(used);
|
|
cursor += used;
|
|
remaining -= static_cast<size_t>(used);
|
|
|
|
if (packetSize > 0)
|
|
{
|
|
++totalPacketsSent;
|
|
if (!sendPacket(packetData, static_cast<size_t>(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<MpegDecodedFrame> &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<size_t>(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<MpegDecodedFrame> &frames)
|
|
{
|
|
if (!data || size == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
av_packet_unref(m_packet);
|
|
const int allocRet = av_new_packet(m_packet, static_cast<int>(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<MpegDecodedFrame> &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<MpegDecodedFrame> &frames)
|
|
{
|
|
const int width = m_frame->width;
|
|
const int height = m_frame->height;
|
|
const AVPixelFormat srcFormat = static_cast<AVPixelFormat>(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<size_t>(width) * static_cast<size_t>(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<MpegDecodedFrame> &)
|
|
{
|
|
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<MpegDecodedFrame> &)
|
|
{
|
|
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<uint8_t> videoSequenceSyncBuffer;
|
|
std::vector<uint8_t> pssBuffer;
|
|
std::vector<uint32_t> pssGuestAddrs;
|
|
std::deque<MpegDecodedFrame> decodedFrames;
|
|
std::unique_ptr<MpegFfmpegDecoder> decoder;
|
|
uint64_t pictureIntervalQ32 = 0u;
|
|
uint64_t nextPictureTickQ32 = std::numeric_limits<uint64_t>::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<MpegRegisteredCallback> 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<uint32_t, std::vector<MpegRegisteredCallback>> callbacksByMpeg;
|
|
std::unordered_map<uint32_t, MpegPlaybackState> 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<size_t>::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<uint16_t>((static_cast<uint16_t>(p[0]) << 8u) |
|
|
static_cast<uint16_t>(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<uint8_t> &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<uint32_t>(data[i + 4u]) << 4u) |
|
|
(static_cast<uint32_t>(data[i + 5u]) >> 4u);
|
|
const uint32_t height = ((static_cast<uint32_t>(data[i + 5u]) & 0x0Fu) << 8u) |
|
|
static_cast<uint32_t>(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<size_t>(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<size_t>(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<std::ptrdiff_t>(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<std::ptrdiff_t>(sequenceHeader));
|
|
}
|
|
|
|
data = playback.videoSequenceSyncBuffer.data();
|
|
size = playback.videoSequenceSyncBuffer.size();
|
|
playback.pictureIntervalQ32 = mpegPictureIntervalQ32(data[7u] & 0x0Fu);
|
|
playback.nextPictureTickQ32 = std::numeric_limits<uint64_t>::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<MpegFfmpegDecoder>();
|
|
}
|
|
|
|
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<uint8_t> &buffer = playback.pssBuffer;
|
|
std::vector<uint32_t> &guestAddrs = playback.pssGuestAddrs;
|
|
const size_t clamped = std::min(count, buffer.size());
|
|
if (clamped == 0u)
|
|
{
|
|
return;
|
|
}
|
|
|
|
buffer.erase(buffer.begin(), buffer.begin() + static_cast<std::ptrdiff_t>(clamped));
|
|
if (guestAddrs.size() >= clamped)
|
|
{
|
|
guestAddrs.erase(guestAddrs.begin(), guestAddrs.begin() + static_cast<std::ptrdiff_t>(clamped));
|
|
}
|
|
else
|
|
{
|
|
guestAddrs.clear();
|
|
}
|
|
}
|
|
|
|
std::vector<MpegRegisteredCallback> matchingStreamCallbacks(uint32_t mpegAddr, uint32_t streamType)
|
|
{
|
|
std::vector<MpegRegisteredCallback> 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<MpegStreamCallbackEvent> &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<MpegStreamCallbackEvent> &callbackEvents,
|
|
bool finalChunk = false)
|
|
{
|
|
std::vector<uint8_t> &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<size_t>(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<size_t>(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<size_t>(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<uint32_t>(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<uint32_t>(packetEnd - payloadStart),
|
|
callbackEvents);
|
|
queueStreamCallbackEvent(
|
|
mpegAddr,
|
|
kMpegStrADPCM,
|
|
playback.pssGuestAddrs[payloadStart],
|
|
static_cast<uint32_t>(packetEnd - payloadStart),
|
|
callbackEvents);
|
|
}
|
|
}
|
|
|
|
erasePssPrefix(playback, packetEnd);
|
|
}
|
|
}
|
|
|
|
void finishPlaybackStream(uint32_t mpegAddr, MpegPlaybackState &playback)
|
|
{
|
|
std::vector<MpegStreamCallbackEvent> ignoredCallbacks;
|
|
processPssBuffer(mpegAddr, playback, ignoredCallbacks, true);
|
|
playback.streamEnded = true;
|
|
playback.cdStreamGeneration = g_mpeg_stub_state.cdStreamGeneration;
|
|
flushDecoderIfEnded(playback);
|
|
}
|
|
|
|
void finalizeCdStreamEofUnlocked(std::vector<uint32_t> &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<uint32_t> &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<MpegStreamCallbackEvent> &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<uint32_t>(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<MpegStreamCallbackEvent> &callbackEvents)
|
|
{
|
|
size_t copied = 0u;
|
|
while (copied < size)
|
|
{
|
|
const uint32_t curAddr = addr + static_cast<uint32_t>(copied);
|
|
const uint32_t offset = curAddr & PS2_RAM_MASK;
|
|
size_t chunk = std::min<size_t>(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<MpegStreamCallbackEvent> &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<uint32_t>(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<uint32_t *>(data + 0x00u) = event.streamType;
|
|
*reinterpret_cast<uint32_t *>(data + 0x08u) = event.dataAddr;
|
|
*reinterpret_cast<uint32_t *>(data + 0x0Cu) = event.len;
|
|
*reinterpret_cast<uint64_t *>(data + 0x10u) = event.pts;
|
|
*reinterpret_cast<uint64_t *>(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<MpegStreamCallbackEvent> &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<MpegStreamCallbackEvent> &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<size_t>(mbx) * static_cast<size_t>(outHeight) * 16u * 4u;
|
|
for (uint32_t y = 0u; y < outHeight; ++y)
|
|
{
|
|
uint8_t *dst = getMemPtr(
|
|
rdram,
|
|
destAddr + static_cast<uint32_t>(stripOffset + static_cast<size_t>(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<uint32_t>(frame.width);
|
|
const uint32_t height = static_cast<uint32_t>(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<size_t>(mbx) * static_cast<size_t>(outHeight) * 16u * 4u;
|
|
for (uint32_t y = 0u; y < outHeight; ++y)
|
|
{
|
|
uint8_t *dst = getMemPtr(
|
|
rdram,
|
|
destAddr + static_cast<uint32_t>(stripOffset + static_cast<size_t>(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<size_t>(y) * static_cast<size_t>(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<std::mutex> 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<size_t>(kTestFrameWidth * kTestFrameHeight * 4), 0x80u);
|
|
|
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<uint32_t> completedMpegIds;
|
|
bool changed = false;
|
|
{
|
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> lock(g_mpeg_stub_mutex);
|
|
MpegPlaybackState &playback = getPlaybackState(mpegAddr);
|
|
while (copied < byteCount)
|
|
{
|
|
const uint32_t curAddr = dataAddr + static_cast<uint32_t>(copied);
|
|
const uint32_t offset = curAddr & PS2_RAM_MASK;
|
|
const size_t chunk = std::min<size_t>(static_cast<size_t>(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<int32_t>(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<std::mutex> 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<std::mutex> 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<uint32_t *>(p);
|
|
if (ptr != 0u)
|
|
{
|
|
uint8_t *q = getMemPtr(rdram, ptr + 0x28u);
|
|
if (q)
|
|
*reinterpret_cast<uint32_t *>(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<uint32_t *>(p) = value;
|
|
}
|
|
static void mpegGuestWrite64(uint8_t *rdram, uint32_t addr, uint64_t value)
|
|
{
|
|
if (uint8_t *p = getMemPtr(rdram, addr))
|
|
{
|
|
*reinterpret_cast<uint32_t *>(p) = static_cast<uint32_t>(value);
|
|
*reinterpret_cast<uint32_t *>(p + 4) = static_cast<uint32_t>(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<int32_t>(param_3) - static_cast<int32_t>(uVar3 - param_2);
|
|
|
|
if (iVar2_signed <= 0x117)
|
|
{
|
|
setReturnU32(ctx, 0u);
|
|
return;
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);
|
|
getPlaybackState(param_1) = makeFreshPlaybackState();
|
|
}
|
|
|
|
const uint32_t puVar4 = uVar3 + 0x108u;
|
|
const uint32_t innerSize = static_cast<uint32_t>(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<uint32_t *>(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<std::mutex> 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<MpegStreamCallbackEvent> callbackEvents;
|
|
std::vector<uint32_t> completedMpegIds;
|
|
size_t consumed = 0u;
|
|
size_t decodedCount = 0u;
|
|
uint32_t traceIdx = 0u;
|
|
bool eofChanged = false;
|
|
{
|
|
std::lock_guard<std::mutex> 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<int32_t>(consumed));
|
|
}
|
|
|
|
void sceMpegDemuxPssRing(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
|
{
|
|
static std::atomic<uint32_t> 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<MpegStreamCallbackEvent> callbackEvents;
|
|
std::vector<uint32_t> completedMpegIds;
|
|
size_t consumed = 0u;
|
|
size_t decodedCount = 0u;
|
|
uint32_t traceIdx = 0u;
|
|
bool eofChanged = false;
|
|
{
|
|
std::lock_guard<std::mutex> 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<int32_t>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<int32_t>(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<uint64_t>::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<int32_t>(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<uint32_t>(frame.width);
|
|
playback.height = static_cast<uint32_t>(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<uint32_t *>(base + 0x40);
|
|
if (uint8_t *inner = getMemPtr(rdram, iVar1))
|
|
{
|
|
*reinterpret_cast<uint32_t *>(inner + 0xb0) = 1;
|
|
*reinterpret_cast<uint32_t *>(inner + 0xd8) = (getRegU32(ctx, 5) & 0x0FFFFFFFu) | 0x20000000u;
|
|
*reinterpret_cast<uint32_t *>(inner + 0xe4) = getRegU32(ctx, 6);
|
|
*reinterpret_cast<uint32_t *>(inner + 0xdc) = 0;
|
|
*reinterpret_cast<uint32_t *>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<uint32_t *>(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<std::mutex> 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<std::mutex> lock(g_mpeg_stub_mutex);
|
|
getPlaybackState(mpegAddr).imageBufferAddr = imageBufferAddr;
|
|
setReturnS32(ctx, 0);
|
|
}
|
|
}
|