// Copyright (c) 2012- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. // This code is part shamelessly "inspired" from JPSCP. #include #include #include "Core/HLE/sceMpeg.h" #include "Core/HLE/sceKernelModule.h" #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HW/MediaEngine.h" #include "Core/Config.h" #include "Core/MemMapHelpers.h" #include "Core/Reporting.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" // MPEG AVC elementary stream. static const int MPEG_AVC_ES_SIZE = 2048; // MPEG packet size. // MPEG ATRAC elementary stream. static const int MPEG_ATRAC_ES_SIZE = 2112; static const int MPEG_ATRAC_ES_OUTPUT_SIZE = 8192; // MPEG PCM elementary stream. static const int MPEG_PCM_ES_SIZE = 320; static const int MPEG_PCM_ES_OUTPUT_SIZE = 320; // MPEG Userdata elementary stream. static const int MPEG_DATA_ES_SIZE = 0xA0000; static const int MPEG_DATA_ES_OUTPUT_SIZE = 0xA0000; static const int MPEG_DATA_ES_BUFFERS = 2; // MPEG analysis results. static const int MPEG_VERSION_0012 = 0; static const int MPEG_VERSION_0013 = 1; static const int MPEG_VERSION_0014 = 2; static const int MPEG_VERSION_0015 = 3; // PSMF analysis results. static const int PSMF_VERSION_0012 = 0x32313030; static const int PSMF_VERSION_0013 = 0x33313030; static const int PSMF_VERSION_0014 = 0x34313030; static const int PSMF_VERSION_0015 = 0x35313030; // MPEG streams. static const int MPEG_AVC_STREAM = 0; static const int MPEG_ATRAC_STREAM = 1; static const int MPEG_PCM_STREAM = 2; static const int MPEG_DATA_STREAM = 3; // Arbitrary user defined type. Can represent audio or video. static const int MPEG_AUDIO_STREAM = 15; static const int MPEG_AU_MODE_DECODE = 0; static const int MPEG_AU_MODE_SKIP = 1; static const u32 MPEG_MEMSIZE = 0x10000; // 64k. static const int MPEG_AVC_DECODE_SUCCESS = 1; // Internal value. static const int atracDecodeDelayMs = 3000; static const int avcFirstDelayMs = 3600; static const int avcCscDelayMs = 4000; static const int avcDecodeDelayMs = 5400; // Varies between 4700 and 6000. static const int avcEmptyDelayMs = 320; static const int mpegDecodeErrorDelayMs = 100; static const int mpegTimestampPerSecond = 90000; // How many MPEG Timestamp units in a second. static const int videoTimestampStep = 3003; // Value based on pmfplayer (mpegTimestampPerSecond / 29.970 (fps)). static const int audioTimestampStep = 4180; // For audio play at 44100 Hz (2048 samples / 44100 * mpegTimestampPerSecond == 4180) static const int audioFirstTimestamp = 90000; // The first MPEG audio AU has always this timestamp static const int maxAheadTimestamp = 40000; static const s64 UNKNOWN_TIMESTAMP = -1; // At least 2048 bytes of MPEG data is provided when analysing the MPEG header static const int MPEG_HEADER_BUFFER_MINIMUM_SIZE = 2048; // For PMP media static u32 pmp_videoSource = 0; //pointer to the video source (SceMpegLLi structure) static int pmp_nBlocks = 0; //number of blocks received in the last sceMpegbase_BEA18F91 call static std::list pmp_queue; //list of pmp video frames have been decoded and will be played static std::list pmp_ContextList; //list of pmp media contexts static bool pmp_oldStateLoaded = false; // for dostate #ifdef USE_FFMPEG extern "C" { #include "libavformat/avformat.h" #include "libswscale/swscale.h" } static AVPixelFormat pmp_want_pix_fmt; #endif struct SceMpegLLI { u32 pSrc; u32 pDst; u32 Next; int iSize; }; void SceMpegAu::read(u32 addr) { Memory::ReadStruct(addr, this); pts = (pts & 0xFFFFFFFFULL) << 32 | (((u64)pts) >> 32); dts = (dts & 0xFFFFFFFFULL) << 32 | (((u64)dts) >> 32); } void SceMpegAu::write(u32 addr) { pts = (pts & 0xFFFFFFFFULL) << 32 | (((u64)pts) >> 32); dts = (dts & 0xFFFFFFFFULL) << 32 | (((u64)dts) >> 32); Memory::WriteStruct(addr, this); } static int getMaxAheadTimestamp(const SceMpegRingBuffer &ringbuf) { return std::max(maxAheadTimestamp, 700 * ringbuf.packets); // empiric value from JPCSP, thanks! } const u8 defaultMpegheader[2048] = {0x50,0x53,0x4d,0x46,0x30,0x30,0x31,0x35,0x00,0x00,0x08,0x00,0x00, 0x10,0xc8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4e,0x00, 0x00,0x00,0x01,0x5f,0x90,0x00,0x00,0x00,0x0d,0xbe,0xca,0x00,0x00,0x61,0xa8,0x00,0x01,0x5f, 0x90,0x02,0x01,0x00,0x00,0x00,0x34,0x00,0x00,0x00,0x01,0x5f,0x90,0x00,0x00,0x00,0x0d,0xbe, 0xca,0x00,0x01,0x00,0x00,0x00,0x22,0x00,0x02,0xe0,0x00,0x20,0xfb,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x1e,0x11,0x00,0x00,0xbd,0x00,0x20,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x02,0x02}; // Internal structure struct AvcContext { int avcDetailFrameWidth; int avcDetailFrameHeight; int avcDecodeResult; int avcFrameStatus; }; struct StreamInfo { int type; int num; int sid; bool needsReset; }; typedef std::map StreamInfoMap; // Internal structure struct MpegContext { MpegContext() : mediaengine(nullptr), ringbufferNeedsReverse(false) { memcpy(mpegheader, defaultMpegheader, 2048); } ~MpegContext() { delete mediaengine; } void DoState(PointerWrap &p) { auto s = p.Section("MpegContext", 1, 2); if (!s) return; p.DoArray(mpegheader, 2048); p.Do(defaultFrameWidth); p.Do(videoFrameCount); p.Do(audioFrameCount); p.Do(endOfAudioReached); p.Do(endOfVideoReached); p.Do(videoPixelMode); p.Do(mpegMagic); p.Do(mpegVersion); p.Do(mpegRawVersion); p.Do(mpegOffset); p.Do(mpegStreamSize); p.Do(mpegFirstTimestamp); p.Do(mpegLastTimestamp); p.Do(mpegFirstDate); p.Do(mpegLastDate); p.Do(mpegRingbufferAddr); p.DoArray(esBuffers, MPEG_DATA_ES_BUFFERS); p.Do(avc); p.Do(avcRegistered); p.Do(atracRegistered); p.Do(pcmRegistered); p.Do(dataRegistered); p.Do(ignoreAtrac); p.Do(ignorePcm); p.Do(ignoreAvc); p.Do(isAnalyzed); p.Do(streamMap); p.DoClass(mediaengine); ringbufferNeedsReverse = s < 2; } u8 mpegheader[2048]; u32 defaultFrameWidth; int videoFrameCount; int audioFrameCount; bool endOfAudioReached; bool endOfVideoReached; int videoPixelMode; u32 mpegMagic; int mpegVersion; u32 mpegRawVersion; u32 mpegOffset; u32 mpegStreamSize; s64 mpegFirstTimestamp; s64 mpegLastTimestamp; u32 mpegFirstDate; u32 mpegLastDate; u32 mpegRingbufferAddr; bool esBuffers[MPEG_DATA_ES_BUFFERS]; AvcContext avc; bool avcRegistered; bool atracRegistered; bool pcmRegistered; bool dataRegistered; bool ignoreAtrac; bool ignorePcm; bool ignoreAvc; bool isAnalyzed; bool ringbufferNeedsReverse; StreamInfoMap streamMap; MediaEngine *mediaengine; }; static bool isMpegInit; static int mpegLibVersion; static u32 streamIdGen; static int actionPostPut; static std::map mpegMap; static MpegContext *getMpegCtx(u32 mpegAddr) { if (!Memory::IsValidAddress(mpegAddr)) return nullptr; u32 mpeg = Memory::Read_U32(mpegAddr); auto found = mpegMap.find(mpeg); if (found == mpegMap.end()) return nullptr; MpegContext *res = found->second; // Take this opportunity to upgrade savestates if necessary. if (res->ringbufferNeedsReverse) { auto ringbuffer = PSPPointer::Create(res->mpegRingbufferAddr); ringbuffer->packetsAvail = ringbuffer->packets - ringbuffer->packetsAvail; res->ringbufferNeedsReverse = false; } return res; } static void InitRingbuffer(SceMpegRingBuffer *buf, int packets, int data, int size, int callback_addr, int callback_args) { buf->packets = packets; buf->packetsRead = 0; buf->packetsWritten = 0; buf->packetsAvail = 0; buf->packetSize = 2048; buf->data = data; buf->callback_addr = callback_addr; buf->callback_args = callback_args; buf->dataUpperBound = data + packets * 2048; buf->semaID = 0; buf->mpeg = 0; // This isn't in ver 0104, but it is in 0105. if (mpegLibVersion >= 0x0105) buf->gp = __KernelGetModuleGP(__KernelGetCurThreadModuleId()); } static u32 convertTimestampToDate(u32 ts) { return ts; // TODO } static u32 getMpegVersion(u32 mpegRawVersion) { switch (mpegRawVersion) { case PSMF_VERSION_0012: return MPEG_VERSION_0012; case PSMF_VERSION_0013: return MPEG_VERSION_0013; case PSMF_VERSION_0014: return MPEG_VERSION_0014; case PSMF_VERSION_0015: return MPEG_VERSION_0015; default: return -1; } } static void AnalyzeMpeg(u8 *buffer, MpegContext *ctx) { ctx->mpegMagic = *(u32_le*)buffer; ctx->mpegRawVersion = *(u32_le*)(buffer + PSMF_STREAM_VERSION_OFFSET); ctx->mpegVersion = getMpegVersion(ctx->mpegRawVersion); ctx->mpegOffset = bswap32(*(u32_le*)(buffer + PSMF_STREAM_OFFSET_OFFSET)); ctx->mpegStreamSize = bswap32(*(u32_le*)(buffer + PSMF_STREAM_SIZE_OFFSET)); ctx->mpegFirstTimestamp = getMpegTimeStamp(buffer + PSMF_FIRST_TIMESTAMP_OFFSET); ctx->mpegLastTimestamp = getMpegTimeStamp(buffer + PSMF_LAST_TIMESTAMP_OFFSET); ctx->mpegFirstDate = convertTimestampToDate(ctx->mpegFirstTimestamp); ctx->mpegLastDate = convertTimestampToDate(ctx->mpegLastTimestamp); ctx->avc.avcDetailFrameWidth = (*(u8*)(buffer + 142)) * 0x10; ctx->avc.avcDetailFrameHeight = (*(u8*)(buffer + 143)) * 0x10; ctx->avc.avcDecodeResult = MPEG_AVC_DECODE_SUCCESS; ctx->avc.avcFrameStatus = 0; ctx->videoFrameCount = 0; ctx->audioFrameCount = 0; ctx->endOfAudioReached = false; ctx->endOfVideoReached = false; // Sanity Check ctx->mpegFirstTimestamp if (ctx->mpegFirstTimestamp != 90000) { WARN_LOG_REPORT(ME, "Unexpected mpeg first timestamp: %llx / %lld", ctx->mpegFirstTimestamp, ctx->mpegFirstTimestamp); } if (ctx->mpegMagic != PSMF_MAGIC || ctx->mpegVersion < 0 || (ctx->mpegOffset & 2047) != 0 || ctx->mpegOffset == 0) { // mpeg header is invalid! return; } if (ctx->mediaengine && (ctx->mpegStreamSize > 0) && !ctx->isAnalyzed) { // init mediaEngine auto ringbuffer = PSPPointer::Create(ctx->mpegRingbufferAddr); if (ringbuffer.IsValid()) { ctx->mediaengine->loadStream(buffer, ctx->mpegOffset, ringbuffer->packets * ringbuffer->packetSize); } else { // TODO: Does this make any sense? ctx->mediaengine->loadStream(buffer, ctx->mpegOffset, 0); } ctx->mediaengine->setVideoDim(); } // When used with scePsmf, some applications attempt to use sceMpegQueryStreamOffset // and sceMpegQueryStreamSize, which forces a packet overwrite in the Media Engine and in // the MPEG ringbuffer. // Mark the current MPEG as analyzed to filter this, and restore it at sceMpegFinish. ctx->isAnalyzed = true; // copy header struct to mpeg header. memcpy(ctx->mpegheader, buffer, 2048); *(u32_le*)(ctx->mpegheader + PSMF_STREAM_OFFSET_OFFSET) = 0x80000; INFO_LOG(ME, "Stream offset: %d, Stream size: 0x%X", ctx->mpegOffset, ctx->mpegStreamSize); INFO_LOG(ME, "First timestamp: %lld, Last timestamp: %lld", ctx->mpegFirstTimestamp, ctx->mpegLastTimestamp); } class PostPutAction : public Action { public: PostPutAction() {} void setRingAddr(u32 ringAddr) { ringAddr_ = ringAddr; } static Action *Create() { return new PostPutAction; } void DoState(PointerWrap &p) override { auto s = p.Section("PostPutAction", 1); if (!s) return; p.Do(ringAddr_); } void run(MipsCall &call) override; private: u32 ringAddr_; }; void __MpegInit() { isMpegInit = false; mpegLibVersion = 0x010A; streamIdGen = 1; actionPostPut = __KernelRegisterActionType(PostPutAction::Create); #ifdef USE_FFMPEG avcodec_register_all(); av_register_all(); #endif } void __MpegDoState(PointerWrap &p) { auto s = p.Section("sceMpeg", 1, 2); if (!s) return; if (s < 2) { int oldLastMpeg = -1; bool oldIsMpegAnalyzed = false; p.Do(oldLastMpeg); p.Do(streamIdGen); p.Do(oldIsMpegAnalyzed); // Let's assume the oldest version. mpegLibVersion = 0x0101; } else { p.Do(streamIdGen); p.Do(mpegLibVersion); } p.Do(isMpegInit); p.Do(actionPostPut); __KernelRestoreActionType(actionPostPut, PostPutAction::Create); p.Do(mpegMap); } void __MpegShutdown() { std::map::iterator it, end; for (it = mpegMap.begin(), end = mpegMap.end(); it != end; ++it) { delete it->second; } mpegMap.clear(); } void __MpegLoadModule(int version) { mpegLibVersion = version; } static u32 sceMpegInit() { if (isMpegInit) { WARN_LOG(ME, "sceMpegInit(): already initialized"); // TODO: Need to properly hook module load/unload for this to work right. //return ERROR_MPEG_ALREADY_INIT; } else { INFO_LOG(ME, "sceMpegInit()"); } isMpegInit = true; return hleDelayResult(0, "mpeg init", 750); } static u32 __MpegRingbufferQueryMemSize(int packets) { return packets * (104 + 2048); } static u32 sceMpegRingbufferQueryMemSize(int packets) { u32 size = __MpegRingbufferQueryMemSize(packets); DEBUG_LOG(ME, "%i = sceMpegRingbufferQueryMemSize(%i)", size, packets); return size; } static u32 sceMpegRingbufferConstruct(u32 ringbufferAddr, u32 numPackets, u32 data, u32 size, u32 callbackAddr, u32 callbackArg) { if (!Memory::IsValidAddress(ringbufferAddr)) { ERROR_LOG_REPORT(ME, "sceMpegRingbufferConstruct(%08x, %i, %08x, %08x, %08x, %08x): bad ringbuffer, should crash", ringbufferAddr, numPackets, data, size, callbackAddr, callbackArg); return SCE_KERNEL_ERROR_ILLEGAL_ADDRESS; } if ((int)size < 0) { ERROR_LOG_REPORT(ME, "sceMpegRingbufferConstruct(%08x, %i, %08x, %08x, %08x, %08x): invalid size", ringbufferAddr, numPackets, data, size, callbackAddr, callbackArg); return ERROR_MPEG_NO_MEMORY; } if (__MpegRingbufferQueryMemSize(numPackets) > size) { if (numPackets < 0x00100000) { ERROR_LOG_REPORT(ME, "sceMpegRingbufferConstruct(%08x, %i, %08x, %08x, %08x, %08x): too many packets for buffer", ringbufferAddr, numPackets, data, size, callbackAddr, callbackArg); return ERROR_MPEG_NO_MEMORY; } else { // The PSP's firmware allows some cases here, due to a bug in its validation. ERROR_LOG_REPORT(ME, "sceMpegRingbufferConstruct(%08x, %i, %08x, %08x, %08x, %08x): too many packets for buffer, bogus size", ringbufferAddr, numPackets, data, size, callbackAddr, callbackArg); } } DEBUG_LOG(ME, "sceMpegRingbufferConstruct(%08x, %i, %08x, %08x, %08x, %08x)", ringbufferAddr, numPackets, data, size, callbackAddr, callbackArg); auto ring = PSPPointer::Create(ringbufferAddr); InitRingbuffer(ring, numPackets, data, size, callbackAddr, callbackArg); return 0; } static u32 sceMpegCreate(u32 mpegAddr, u32 dataPtr, u32 size, u32 ringbufferAddr, u32 frameWidth, u32 mode, u32 ddrTop) { if (!Memory::IsValidAddress(mpegAddr)) { WARN_LOG(ME, "sceMpegCreate(%08x, %08x, %i, %08x, %i, %i, %i): invalid addresses", mpegAddr, dataPtr, size, ringbufferAddr, frameWidth, mode, ddrTop); return -1; } if (size < MPEG_MEMSIZE) { WARN_LOG(ME, "ERROR_MPEG_NO_MEMORY=sceMpegCreate(%08x, %08x, %i, %08x, %i, %i, %i)", mpegAddr, dataPtr, size, ringbufferAddr, frameWidth, mode, ddrTop); return ERROR_MPEG_NO_MEMORY; } auto ringbuffer = PSPPointer::Create(ringbufferAddr); if (ringbuffer.IsValid()) { if (ringbuffer->packetSize == 0) { ringbuffer->packetsAvail = 0; } else { ringbuffer->packetsAvail = ringbuffer->packets - (ringbuffer->dataUpperBound - ringbuffer->data) / ringbuffer->packetSize; } ringbuffer->mpeg = mpegAddr; } // Generate, and write mpeg handle into mpeg data, for some reason int mpegHandle = dataPtr + 0x30; Memory::Write_U32(mpegHandle, mpegAddr); // Initialize fake mpeg struct. Memory::Memcpy(mpegHandle, "LIBMPEG\0", 8); Memory::Memcpy(mpegHandle + 8, "001\0", 4); Memory::Write_U32(-1, mpegHandle + 12); if (ringbuffer.IsValid()) { Memory::Write_U32(ringbufferAddr, mpegHandle + 16); Memory::Write_U32(ringbuffer->dataUpperBound, mpegHandle + 20); } MpegContext *ctx = new MpegContext; if (mpegMap.find(mpegHandle) != mpegMap.end()) { WARN_LOG_REPORT(HLE, "Replacing existing mpeg context at %08x", mpegAddr); // Otherwise, it would leak. delete mpegMap[mpegHandle]; } mpegMap[mpegHandle] = ctx; // Initialize mpeg values. ctx->mpegRingbufferAddr = ringbufferAddr; ctx->videoFrameCount = 0; ctx->audioFrameCount = 0; ctx->videoPixelMode = GE_CMODE_32BIT_ABGR8888; // TODO: What's the actual default? ctx->avcRegistered = false; ctx->atracRegistered = false; ctx->pcmRegistered = false; ctx->dataRegistered = false; ctx->ignoreAtrac = false; ctx->ignorePcm = false; ctx->ignoreAvc = false; ctx->defaultFrameWidth = frameWidth; for (int i = 0; i < MPEG_DATA_ES_BUFFERS; i++) { ctx->esBuffers[i] = false; } // Detailed "analysis" is done later in Query* for some reason. ctx->isAnalyzed = false; ctx->mediaengine = new MediaEngine(); INFO_LOG(ME, "%08x=sceMpegCreate(%08x, %08x, %i, %08x, %i, %i, %i)", mpegHandle, mpegAddr, dataPtr, size, ringbufferAddr, frameWidth, mode, ddrTop); return hleDelayResult(0, "mpeg create", 29000); } static int sceMpegDelete(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegDelete(%08x): bad mpeg handle", mpeg); return -1; } DEBUG_LOG(ME, "sceMpegDelete(%08x)", mpeg); delete ctx; mpegMap.erase(Memory::Read_U32(mpeg)); return 0; } static int sceMpegAvcDecodeMode(u32 mpeg, u32 modeAddr) { if (!Memory::IsValidAddress(modeAddr)) { WARN_LOG(ME, "sceMpegAvcDecodeMode(%08x, %08x): invalid addresses", mpeg, modeAddr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegAvcDecodeMode(%08x, %08x): bad mpeg handle", mpeg, modeAddr); return -1; } DEBUG_LOG(ME, "sceMpegAvcDecodeMode(%08x, %08x)", mpeg, modeAddr); int mode = Memory::Read_U32(modeAddr); int pixelMode = Memory::Read_U32(modeAddr + 4); if (pixelMode >= GE_CMODE_16BIT_BGR5650 && pixelMode <= GE_CMODE_32BIT_ABGR8888) { ctx->videoPixelMode = pixelMode; } else { ERROR_LOG(ME, "sceMpegAvcDecodeMode(%i, %i): unknown pixelMode ", mode, pixelMode); } return 0; } static int sceMpegQueryStreamOffset(u32 mpeg, u32 bufferAddr, u32 offsetAddr) { if (!Memory::IsValidAddress(bufferAddr) || !Memory::IsValidAddress(offsetAddr)) { ERROR_LOG(ME, "sceMpegQueryStreamOffset(%08x, %08x, %08x): invalid addresses", mpeg, bufferAddr, offsetAddr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegQueryStreamOffset(%08x, %08x, %08x): bad mpeg handle", mpeg, bufferAddr, offsetAddr); return -1; } DEBUG_LOG(ME, "sceMpegQueryStreamOffset(%08x, %08x, %08x)", mpeg, bufferAddr, offsetAddr); // Kinda destructive, no? AnalyzeMpeg(Memory::GetPointer(bufferAddr), ctx); if (ctx->mpegMagic != PSMF_MAGIC) { ERROR_LOG(ME, "sceMpegQueryStreamOffset: Bad PSMF magic"); Memory::Write_U32(0, offsetAddr); return ERROR_MPEG_INVALID_VALUE; } else if (ctx->mpegVersion < 0) { ERROR_LOG(ME, "sceMpegQueryStreamOffset: Bad version"); Memory::Write_U32(0, offsetAddr); return ERROR_MPEG_BAD_VERSION; } else if ((ctx->mpegOffset & 2047) != 0 || ctx->mpegOffset == 0) { ERROR_LOG(ME, "sceMpegQueryStreamOffset: Bad offset"); Memory::Write_U32(0, offsetAddr); return ERROR_MPEG_INVALID_VALUE; } Memory::Write_U32(ctx->mpegOffset, offsetAddr); return 0; } static u32 sceMpegQueryStreamSize(u32 bufferAddr, u32 sizeAddr) { if (!Memory::IsValidAddress(bufferAddr) || !Memory::IsValidAddress(sizeAddr)) { ERROR_LOG(ME, "sceMpegQueryStreamSize(%08x, %08x): invalid addresses", bufferAddr, sizeAddr); return -1; } DEBUG_LOG(ME, "sceMpegQueryStreamSize(%08x, %08x)", bufferAddr, sizeAddr); MpegContext ctx; ctx.mediaengine = 0; AnalyzeMpeg(Memory::GetPointer(bufferAddr), &ctx); if (ctx.mpegMagic != PSMF_MAGIC) { ERROR_LOG(ME, "sceMpegQueryStreamSize: Bad PSMF magic"); Memory::Write_U32(0, sizeAddr); return ERROR_MPEG_INVALID_VALUE; } else if ((ctx.mpegOffset & 2047) != 0 ) { ERROR_LOG(ME, "sceMpegQueryStreamSize: Bad offset"); Memory::Write_U32(0, sizeAddr); return ERROR_MPEG_INVALID_VALUE; } Memory::Write_U32(ctx.mpegStreamSize, sizeAddr); return 0; } static int sceMpegRegistStream(u32 mpeg, u32 streamType, u32 streamNum) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegRegistStream(%08x, %i, %i): bad mpeg handle", mpeg, streamType, streamNum); return -1; } INFO_LOG(ME, "sceMpegRegistStream(%08x, %i, %i)", mpeg, streamType, streamNum); switch (streamType) { case MPEG_AVC_STREAM: ctx->avcRegistered = true; // TODO: Probably incorrect? ctx->mediaengine->setVideoStream(streamNum); break; case MPEG_AUDIO_STREAM: case MPEG_ATRAC_STREAM: ctx->atracRegistered = true; // TODO: Probably incorrect? ctx->mediaengine->setAudioStream(streamNum); break; case MPEG_PCM_STREAM: ctx->pcmRegistered = true; break; case MPEG_DATA_STREAM: ctx->dataRegistered = true; break; default : DEBUG_LOG(ME, "sceMpegRegistStream(%i) : unknown stream type", streamType); break; } // ... u32 sid = streamIdGen++; StreamInfo info; info.type = streamType; info.num = streamNum; info.sid = sid; info.needsReset = true; ctx->streamMap[sid] = info; return sid; } static int sceMpegMallocAvcEsBuf(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegMallocAvcEsBuf(%08x): bad mpeg handle", mpeg); return -1; } DEBUG_LOG(ME, "sceMpegMallocAvcEsBuf(%08x)", mpeg); // Doesn't actually malloc, just keeps track of a couple of flags for (int i = 0; i < MPEG_DATA_ES_BUFFERS; i++) { if (!ctx->esBuffers[i]) { ctx->esBuffers[i] = true; return i + 1; } } // No es buffer return 0; } static int sceMpegFreeAvcEsBuf(u32 mpeg, int esBuf) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegFreeAvcEsBuf(%08x, %i): bad mpeg handle", mpeg, esBuf); return -1; } DEBUG_LOG(ME, "sceMpegFreeAvcEsBuf(%08x, %i)", mpeg, esBuf); if (esBuf == 0) { return ERROR_MPEG_INVALID_VALUE; } if (esBuf >= 1 && esBuf <= MPEG_DATA_ES_BUFFERS) { // TODO: Check if it's already been free'd? ctx->esBuffers[esBuf - 1] = false; } return 0; } // this function is used for dumping a video frame into a file. // you can use it when you want to output the decoded frame. static void SaveFrame(AVFrame *pFrame, int width, int height) { FILE *pFile; char szFilename[] = "frame.ppm"; int y; pFile = fopen(szFilename, "wb"); if (!pFile) return; // width * 4 is for 32-bit RGBA format. // You could change to your desired format as width*(4:32-bit,3:24-bit, 2:16-bit,1:8-bit) for (y = 0; y < height; y++) fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 4, pFile); fclose(pFile); } // check the existence of pmp media context static bool isContextExist(u32 ctxAddr){ for (auto it = pmp_ContextList.begin(); it != pmp_ContextList.end(); ++it){ if (*it == ctxAddr){ return true; } } return false; } // Initialize Pmp video parameters and decoder. static bool InitPmp(MpegContext * ctx){ #ifdef USE_FFMPEG InitFFmpeg(); auto mediaengine = ctx->mediaengine; mediaengine->m_isVideoEnd = false; mediaengine->m_firstTimeStamp = 0; mediaengine->m_lastTimeStamp = 0; ctx->mpegFirstTimestamp = 0; ctx->mpegLastTimestamp = 0; // wanted output pixel format // reference values for pix_fmt: // GE_CMODE_16BIT_BGR5650 <--> AV_PIX_FMT_BGR565LE // GE_CMODE_16BIT_ABGR5551 <--> AV_PIX_FMT_BGR555LE; // GE_CMODE_16BIT_ABGR4444 <--> AV_PIX_FMT_BGR444LE; // GE_CMODE_32BIT_ABGR8888 <--> AV_PIX_FMT_RGBA; pmp_want_pix_fmt = AV_PIX_FMT_RGBA; // Create H264 video codec AVCodec * pmp_Codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (pmp_Codec == NULL){ ERROR_LOG(ME, "Can not find H264 codec, please update ffmpeg"); return false; } // Create CodecContext AVCodecContext * pmp_CodecCtx = avcodec_alloc_context3(pmp_Codec); if (pmp_CodecCtx == NULL){ ERROR_LOG(ME, "Can not allocate pmp Codec Context"); return false; } // each pmp video context is corresponding to one pmp video codec mediaengine->m_pCodecCtxs[0] = pmp_CodecCtx; // initialize H264 video parameters // set pmp video size. Better to get from pmp file directly if possible. Any idea? pmp_CodecCtx->width = 480; pmp_CodecCtx->height = 272; mediaengine->m_desHeight = pmp_CodecCtx->height; mediaengine->m_desWidth = pmp_CodecCtx->width; // Open pmp video codec if (avcodec_open2(pmp_CodecCtx, pmp_Codec, NULL) < 0){ ERROR_LOG(ME, "Can not open pmp video codec"); return false; } // initialize ctx->mediaengine->m_pFrame and ctx->mediaengine->m_pFrameRGB if (!mediaengine->m_pFrame){ mediaengine->m_pFrame = av_frame_alloc(); } if (!mediaengine->m_pFrameRGB){ mediaengine->m_pFrameRGB = av_frame_alloc(); } // get RGBA picture buffer mediaengine->m_bufSize = avpicture_get_size(pmp_want_pix_fmt, pmp_CodecCtx->width, pmp_CodecCtx->height); mediaengine->m_buffer = (u8*)av_malloc(mediaengine->m_bufSize); return true; #else // we can not play pmp video without ffmpeg return false; #endif } // This class H264Frames is used for collecting small pieces of frames into larger frames for ffmpeg to decode // Basically, this will avoid incomplete frame decoding issue and improve much better the video quality. class H264Frames{ public: int size; u8* stream; H264Frames() :size(0), stream(NULL){}; H264Frames(u8* str, int sz) :size(sz){ stream = new u8[size]; memcpy(stream, str, size); }; H264Frames(H264Frames *frame){ size = frame->size; stream = new u8[size]; memcpy(stream, frame->stream, size); }; ~H264Frames(){ size = 0; if (stream){ delete[] stream; stream = NULL; } }; void add(H264Frames *p){ add(p->stream, p->size); }; void add(u8* str, int sz){ int newsize = size + sz; u8* newstream = new u8[newsize]; // join two streams memcpy(newstream, stream, size); memcpy(newstream + size, str, sz); // delete old stream delete[] stream; // replace with new stream stream = newstream; size = newsize; }; void remove(int pos){ // remove stream from begining to pos if (pos == 0){ // nothing to remove } else if (pos >= size){ // we remove all size = 0; if (stream){ delete[] stream; stream = NULL; } } else{ // we remove the front part size -= pos; u8* str = new u8[size]; memcpy(str, stream + pos, size); delete[] stream; stream = str; } }; #ifndef USE_FFMPEG #define FF_INPUT_BUFFER_PADDING_SIZE 16 #endif void addpadding(){ u8* str = new u8[size + FF_INPUT_BUFFER_PADDING_SIZE]; memcpy(str, stream, size); memset(str + size, 0, FF_INPUT_BUFFER_PADDING_SIZE); size += FF_INPUT_BUFFER_PADDING_SIZE; delete[] stream; stream = str; } }; // collect pmp video frames static H264Frames *pmpframes; // decode pmp video to RGBA format static bool decodePmpVideo(PSPPointer ringbuffer, u32 pmpctxAddr){ // the current video is pmp iff pmp_videoSource is a valid addresse MpegContext* ctx = getMpegCtx(pmpctxAddr); if (Memory::IsValidAddress(pmp_videoSource)){ // We should initialize pmp codec for each pmp context if (isContextExist(pmpctxAddr) == false){ bool ret = InitPmp(ctx); if (!ret){ ERROR_LOG(ME, "Pmp video initialization failed"); return false; } // add the initialized context into ContextList pmp_ContextList.push_front(pmpctxAddr); } ringbuffer->packetsRead = pmp_nBlocks; MediaEngine* mediaengine = ctx->mediaengine; AVFrame* pFrame = mediaengine->m_pFrame; AVFrame* pFrameRGB = mediaengine->m_pFrameRGB; auto pCodecCtx = mediaengine->m_pCodecCtxs[0]; // pmpframes could be destroied when close a video to load another one if (!pmpframes) pmpframes = new H264Frames; // joint all blocks into H264Frames SceMpegLLI lli; for (int i = 0; i < pmp_nBlocks; i++){ Memory::ReadStructUnchecked(pmp_videoSource, &lli); // add source block into pmpframes pmpframes->add(Memory::GetPointer(lli.pSrc), lli.iSize); // get next block pmp_videoSource += sizeof(SceMpegLLI); } pmpframes->addpadding(); // initialize packet AVPacket packet; av_new_packet(&packet, pCodecCtx->width*pCodecCtx->height); // set packet to source block packet.data = pmpframes->stream; packet.size = pmpframes->size; // reuse pFrame and pFrameRGB int got_picture = 0; av_frame_unref(pFrame); av_frame_unref(pFrameRGB); // hook pFrameRGB output to buffer avpicture_fill((AVPicture *)pFrameRGB, mediaengine->m_buffer, pmp_want_pix_fmt, pCodecCtx->width, pCodecCtx->height); // decode video frame int len = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet); DEBUG_LOG(ME, "got_picture %d", got_picture); if (got_picture){ SwsContext *img_convert_ctx = NULL; img_convert_ctx = sws_getContext( pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, pmp_want_pix_fmt, SWS_BILINEAR, NULL, NULL, NULL); if (!img_convert_ctx) { ERROR_LOG(ME, "Cannot initialize sws conversion context"); return false; } // Convert to RGBA int swsRet = sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); if (swsRet < 0){ ERROR_LOG(ME, "sws_scale: Error while converting %d", swsRet); return false; } // free sws context sws_freeContext(img_convert_ctx); // update timestamp if (av_frame_get_best_effort_timestamp(mediaengine->m_pFrame) != AV_NOPTS_VALUE) mediaengine->m_videopts = av_frame_get_best_effort_timestamp(mediaengine->m_pFrame) + av_frame_get_pkt_duration(mediaengine->m_pFrame) - mediaengine->m_firstTimeStamp; else mediaengine->m_videopts += av_frame_get_pkt_duration(mediaengine->m_pFrame); // push the decoded frame into pmp_queue pmp_queue.push_back(pFrameRGB); // write frame into ppm file // SaveFrame(pNewFrameRGB, pCodecCtx->width, pCodecCtx->height); } // free some pointers av_free_packet(&packet); pmpframes->~H264Frames(); // must reset pmp_VideoSource address to zero after decoding. pmp_videoSource = 0; return true; } // not a pmp video, return false return false; } void __VideoPmpInit() { pmp_oldStateLoaded = false; pmpframes = new H264Frames(); } void __VideoPmpShutdown() { #ifdef USE_FFMPEG // We need to empty pmp_queue to not leak memory. for (auto it = pmp_queue.begin(); it != pmp_queue.end(); ++it){ av_free(*it); } pmp_queue.clear(); pmp_ContextList.clear(); delete pmpframes; pmpframes = NULL; #endif } void __VideoPmpDoState(PointerWrap &p){ auto s = p.Section("PMPVideo", 0, 1); if (!s) { if (p.mode == PointerWrap::MODE_READ) pmp_oldStateLoaded = true; return; } p.Do(pmp_videoSource); p.Do(pmp_nBlocks); if (p.mode == PointerWrap::MODE_READ){ // for loadstate, we will reinitialize the pmp codec __VideoPmpShutdown(); } } static u32 sceMpegAvcDecode(u32 mpeg, u32 auAddr, u32 frameWidth, u32 bufferAddr, u32 initAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegAvcDecode(%08x, %08x, %d, %08x, %08x): bad mpeg handle", mpeg, auAddr, frameWidth, bufferAddr, initAddr); return -1; } if (frameWidth == 0) { // wtf, go sudoku passes in 0xcccccccc if (!ctx->defaultFrameWidth) { frameWidth = ctx->avc.avcDetailFrameWidth; } else { frameWidth = ctx->defaultFrameWidth; } } SceMpegAu avcAu; avcAu.read(auAddr); auto ringbuffer = PSPPointer::Create(ctx->mpegRingbufferAddr); if (!ringbuffer.IsValid()) { ERROR_LOG(ME, "Bogus mpegringbufferaddr"); return -1; } u32 buffer = Memory::Read_U32(bufferAddr); u32 init = Memory::Read_U32(initAddr); DEBUG_LOG(ME, "video: bufferAddr = %08x, *buffer = %08x, *init = %08x", bufferAddr, buffer, init); // check and decode pmp video bool ispmp = false; if (decodePmpVideo(ringbuffer, mpeg)){ DEBUG_LOG(ME, "Using ffmpeg to decode pmp video"); ispmp = true; } if (ringbuffer->packetsRead == 0 || ctx->mediaengine->IsVideoEnd()) { WARN_LOG(ME, "sceMpegAvcDecode(%08x, %08x, %d, %08x, %08x): mpeg buffer empty", mpeg, auAddr, frameWidth, bufferAddr, initAddr); return hleDelayResult(ERROR_MPEG_AVC_DECODE_FATAL, "mpeg buffer empty", avcEmptyDelayMs); } // We stored the video stream id here in sceMpegGetAvcAu(). ctx->mediaengine->setVideoStream(avcAu.esBuffer); if (ispmp){ while (pmp_queue.size() != 0){ // playing all pmp_queue frames ctx->mediaengine->m_pFrameRGB = pmp_queue.front(); int bufferSize = ctx->mediaengine->writeVideoImage(buffer, frameWidth, ctx->videoPixelMode); gpu->InvalidateCache(buffer, bufferSize, GPU_INVALIDATE_SAFE); ctx->avc.avcFrameStatus = 1; ctx->videoFrameCount++; // free front frame hleDelayResult(0, "pmp video decode", 30); pmp_queue.pop_front(); } } else if(ctx->mediaengine->stepVideo(ctx->videoPixelMode)) { int bufferSize = ctx->mediaengine->writeVideoImage(buffer, frameWidth, ctx->videoPixelMode); gpu->InvalidateCache(buffer, bufferSize, GPU_INVALIDATE_SAFE); ctx->avc.avcFrameStatus = 1; ctx->videoFrameCount++; } else { ctx->avc.avcFrameStatus = 0; } ringbuffer->packetsAvail = ringbuffer->packets - ctx->mediaengine->getRemainSize() / 2048; avcAu.pts = ctx->mediaengine->getVideoTimeStamp() + ctx->mpegFirstTimestamp; // Flush structs back to memory avcAu.write(auAddr); // Save the current frame's status to initAddr Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr); ctx->avc.avcDecodeResult = MPEG_AVC_DECODE_SUCCESS; DEBUG_LOG(ME, "sceMpegAvcDecode(%08x, %08x, %i, %08x, %08x)", mpeg, auAddr, frameWidth, bufferAddr, initAddr); if (ctx->videoFrameCount <= 1) return hleDelayResult(0, "mpeg decode", avcFirstDelayMs); else return hleDelayResult(0, "mpeg decode", avcDecodeDelayMs); //hleEatMicro(3300); //return hleDelayResult(0, "mpeg decode", 200); } static u32 sceMpegAvcDecodeStop(u32 mpeg, u32 frameWidth, u32 bufferAddr, u32 statusAddr) { if (!Memory::IsValidAddress(bufferAddr) || !Memory::IsValidAddress(statusAddr)){ ERROR_LOG(ME, "sceMpegAvcDecodeStop(%08x, %08x, %08x, %08x): invalid addresses", mpeg, frameWidth, bufferAddr, statusAddr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegAvcDecodeStop(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, frameWidth, bufferAddr, statusAddr); return -1; } DEBUG_LOG(ME, "sceMpegAvcDecodeStop(%08x, %08x, %08x, %08x)", mpeg, frameWidth, bufferAddr, statusAddr); // No last frame generated Memory::Write_U32(0, statusAddr); return 0; } static u32 sceMpegUnRegistStream(u32 mpeg, int streamUid) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegUnRegistStream(%08x, %i): bad mpeg handle", mpeg, streamUid); return -1; } StreamInfo info = {0}; switch (info.type) { case MPEG_AVC_STREAM: ctx->avcRegistered = false; break; case MPEG_AUDIO_STREAM: case MPEG_ATRAC_STREAM: ctx->atracRegistered = false; break; case MPEG_PCM_STREAM: ctx->pcmRegistered = false; break; case MPEG_DATA_STREAM: ctx->dataRegistered = false; break; default : DEBUG_LOG(ME, "sceMpegUnRegistStream(%i) : unknown streamID ", streamUid); break; } ctx->streamMap[streamUid] = info; info.type = -1; info.sid = -1 ; info.needsReset = true; ctx->isAnalyzed = false; return 0; } static int sceMpegAvcDecodeDetail(u32 mpeg, u32 detailAddr) { if (!Memory::IsValidAddress(detailAddr)){ WARN_LOG(ME, "sceMpegAvcDecodeDetail(%08x, %08x): invalid addresses", mpeg, detailAddr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegAvcDecodeDetail(%08x, %08x): bad mpeg handle", mpeg, detailAddr); return -1; } DEBUG_LOG(ME, "sceMpegAvcDecodeDetail(%08x, %08x)", mpeg, detailAddr); Memory::Write_U32(ctx->avc.avcDecodeResult, detailAddr + 0); Memory::Write_U32(ctx->videoFrameCount, detailAddr + 4); Memory::Write_U32(ctx->avc.avcDetailFrameWidth, detailAddr + 8); Memory::Write_U32(ctx->avc.avcDetailFrameHeight, detailAddr + 12); Memory::Write_U32(0, detailAddr + 16); Memory::Write_U32(0, detailAddr + 20); Memory::Write_U32(0, detailAddr + 24); Memory::Write_U32(0, detailAddr + 28); Memory::Write_U32(ctx->avc.avcFrameStatus, detailAddr + 32); return 0; } static u32 sceMpegAvcDecodeStopYCbCr(u32 mpeg, u32 bufferAddr, u32 statusAddr) { if (!Memory::IsValidAddress(bufferAddr) || !Memory::IsValidAddress(statusAddr)) { ERROR_LOG(ME, "UNIMPL sceMpegAvcDecodeStopYCbCr(%08x, %08x, %08x): invalid addresses", mpeg, bufferAddr, statusAddr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegAvcDecodeStopYCbCr(%08x, %08x, %08x): bad mpeg handle", mpeg, bufferAddr, statusAddr); return -1; } ERROR_LOG(ME, "UNIMPL sceMpegAvcDecodeStopYCbCr(%08x, %08x, %08x)", mpeg, bufferAddr, statusAddr); Memory::Write_U32(0, statusAddr); return 0; } static int sceMpegAvcDecodeYCbCr(u32 mpeg, u32 auAddr, u32 bufferAddr, u32 initAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, auAddr, bufferAddr, initAddr); return -1; } SceMpegAu avcAu; avcAu.read(auAddr); auto ringbuffer = PSPPointer::Create(ctx->mpegRingbufferAddr); if (!ringbuffer.IsValid()) { ERROR_LOG(ME, "Bogus mpegringbufferaddr"); return -1; } if (ringbuffer->packetsRead == 0 || ctx->mediaengine->IsVideoEnd()) { WARN_LOG(ME, "sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x): mpeg buffer empty", mpeg, auAddr, bufferAddr, initAddr); return hleDelayResult(ERROR_MPEG_AVC_DECODE_FATAL, "mpeg buffer empty", avcEmptyDelayMs); } // We stored the video stream id here in sceMpegGetAvcAu(). ctx->mediaengine->setVideoStream(avcAu.esBuffer); u32 buffer = Memory::Read_U32(bufferAddr); u32 init = Memory::Read_U32(initAddr); DEBUG_LOG(ME, "*buffer = %08x, *init = %08x", buffer, init); if (ctx->mediaengine->stepVideo(ctx->videoPixelMode)) { // Don't draw here, we'll draw in the Csc func. ctx->avc.avcFrameStatus = 1; ctx->videoFrameCount++; }else { ctx->avc.avcFrameStatus = 0; } ringbuffer->packetsAvail = ringbuffer->packets - ctx->mediaengine->getRemainSize() / 2048; avcAu.pts = ctx->mediaengine->getVideoTimeStamp() + ctx->mpegFirstTimestamp; // Flush structs back to memory avcAu.write(auAddr); // Save the current frame's status to initAddr Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr); ctx->avc.avcDecodeResult = MPEG_AVC_DECODE_SUCCESS; DEBUG_LOG(ME, "sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x)", mpeg, auAddr, bufferAddr, initAddr); if (ctx->videoFrameCount <= 1) return hleDelayResult(0, "mpeg decode", avcFirstDelayMs); else return hleDelayResult(0, "mpeg decode", avcDecodeDelayMs); //hleEatMicro(3300); //return hleDelayResult(0, "mpeg decode", 200); } static u32 sceMpegAvcDecodeFlush(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegAvcDecodeFlush(%08x): bad mpeg handle", mpeg); return -1; } ERROR_LOG(ME, "UNIMPL sceMpegAvcDecodeFlush(%08x)", mpeg); if ( ctx->videoFrameCount > 0 || ctx->audioFrameCount > 0) { //__MpegFinish(); } return 0; } static int sceMpegInitAu(u32 mpeg, u32 bufferAddr, u32 auPointer) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegInitAu(%08x, %i, %08x): bad mpeg handle", mpeg, bufferAddr, auPointer); return -1; } DEBUG_LOG(ME, "sceMpegInitAu(%08x, %i, %08x)", mpeg, bufferAddr, auPointer); SceMpegAu sceAu; sceAu.read(auPointer); if (bufferAddr >= 1 && bufferAddr <= (u32)MPEG_DATA_ES_BUFFERS && ctx->esBuffers[bufferAddr - 1]) { // This esbuffer has been allocated for Avc. sceAu.esBuffer = bufferAddr; // Can this be right??? not much of a buffer pointer.. sceAu.esSize = MPEG_AVC_ES_SIZE; sceAu.dts = 0; sceAu.pts = 0; sceAu.write(auPointer); } else { // This esbuffer has been left as Atrac. sceAu.esBuffer = bufferAddr; sceAu.esSize = MPEG_ATRAC_ES_SIZE; sceAu.pts = 0; sceAu.dts = UNKNOWN_TIMESTAMP; sceAu.write(auPointer); } return 0; } static int sceMpegQueryAtracEsSize(u32 mpeg, u32 esSizeAddr, u32 outSizeAddr) { if (!Memory::IsValidAddress(esSizeAddr) || !Memory::IsValidAddress(outSizeAddr)) { ERROR_LOG(ME, "sceMpegQueryAtracEsSize(%08x, %08x, %08x): invalid addresses", mpeg, esSizeAddr, outSizeAddr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegQueryAtracEsSize(%08x, %08x, %08x): bad mpeg handle", mpeg, esSizeAddr, outSizeAddr); return -1; } DEBUG_LOG(ME, "sceMpegQueryAtracEsSize(%08x, %08x, %08x)", mpeg, esSizeAddr, outSizeAddr); Memory::Write_U32(MPEG_ATRAC_ES_SIZE, esSizeAddr); Memory::Write_U32(MPEG_ATRAC_ES_OUTPUT_SIZE, outSizeAddr); return 0; } static int sceMpegRingbufferAvailableSize(u32 ringbufferAddr) { auto ringbuffer = PSPPointer::Create(ringbufferAddr); if (!ringbuffer.IsValid()) { ERROR_LOG(ME, "sceMpegRingbufferAvailableSize(%08x): invalid ringbuffer, should crash", ringbufferAddr); return SCE_KERNEL_ERROR_ILLEGAL_ADDRESS; } MpegContext *ctx = getMpegCtx(ringbuffer->mpeg); if (!ctx) { ERROR_LOG(ME, "sceMpegRingbufferAvailableSize(%08x): bad mpeg handle", ringbufferAddr); return ERROR_MPEG_NOT_YET_INIT; } ctx->mpegRingbufferAddr = ringbufferAddr; hleEatCycles(2020); hleReSchedule("mpeg ringbuffer avail"); static int lastAvail = 0; if (lastAvail != ringbuffer->packetsAvail) { DEBUG_LOG(ME, "%i=sceMpegRingbufferAvailableSize(%08x)", ringbuffer->packets - ringbuffer->packetsAvail, ringbufferAddr); lastAvail = ringbuffer->packetsAvail; } else { VERBOSE_LOG(ME, "%i=sceMpegRingbufferAvailableSize(%08x)", ringbuffer->packets - ringbuffer->packetsAvail, ringbufferAddr); } return ringbuffer->packets - ringbuffer->packetsAvail; } void PostPutAction::run(MipsCall &call) { auto ringbuffer = PSPPointer::Create(ringAddr_); MpegContext *ctx = getMpegCtx(ringbuffer->mpeg); int packetsAdded = currentMIPS->r[MIPS_REG_V0]; if (ringbuffer->packetsRead == 0 && ctx->mediaengine && packetsAdded > 0) { // init mediaEngine AnalyzeMpeg(ctx->mpegheader, ctx); ctx->mediaengine->loadStream(ctx->mpegheader, 2048, ringbuffer->packets * ringbuffer->packetSize); } if (packetsAdded > 0) { if (packetsAdded > ringbuffer->packets - ringbuffer->packetsAvail) { WARN_LOG(ME, "sceMpegRingbufferPut clamping packetsAdded old=%i new=%i", packetsAdded, ringbuffer->packets - ringbuffer->packetsAvail); packetsAdded = ringbuffer->packets - ringbuffer->packetsAvail; } int actuallyAdded = ctx->mediaengine == NULL ? 8 : ctx->mediaengine->addStreamData(Memory::GetPointer(ringbuffer->data), packetsAdded * 2048) / 2048; if (actuallyAdded != packetsAdded) { WARN_LOG_REPORT(ME, "sceMpegRingbufferPut(): unable to enqueue all added packets, going to overwrite some frames."); } ringbuffer->packetsRead += packetsAdded; ringbuffer->packetsWritten += packetsAdded; ringbuffer->packetsAvail += packetsAdded; } DEBUG_LOG(ME, "packetAdded: %i packetsRead: %i packetsTotal: %i", packetsAdded, ringbuffer->packetsRead, ringbuffer->packets); call.setReturnValue(packetsAdded); } // Program signals that it has written data to the ringbuffer and gets a callback ? static u32 sceMpegRingbufferPut(u32 ringbufferAddr, u32 numPackets, u32 available) { numPackets = std::min(numPackets, available); if (numPackets <= 0) { DEBUG_LOG(ME, "sceMpegRingbufferPut(%08x, %i, %i): no packets to enqueue", ringbufferAddr, numPackets, available); return 0; } auto ringbuffer = PSPPointer::Create(ringbufferAddr); if (!ringbuffer.IsValid()) { // Would have crashed before, TODO test behavior. ERROR_LOG_REPORT(ME, "sceMpegRingbufferPut(%08x, %i, %i): invalid ringbuffer address", ringbufferAddr, numPackets, available); return -1; } MpegContext *ctx = getMpegCtx(ringbuffer->mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegRingbufferPut(%08x, %i, %i): bad mpeg handle %08x", ringbufferAddr, numPackets, available, ringbuffer->mpeg); return -1; } // Execute callback function as a direct MipsCall, no blocking here so no messing around with wait states etc if (ringbuffer->callback_addr != 0) { DEBUG_LOG(ME, "sceMpegRingbufferPut(%08x, %i, %i)", ringbufferAddr, numPackets, available); PostPutAction *action = (PostPutAction *)__KernelCreateAction(actionPostPut); action->setRingAddr(ringbufferAddr); // TODO: Should call this multiple times until we get numPackets. // Normally this would be if it did not read enough, but also if available > packets. // Should ultimately return the TOTAL number of returned packets. u32 packetsThisRound = std::min(numPackets, (u32)ringbuffer->packets); u32 args[3] = {(u32)ringbuffer->data, packetsThisRound, (u32)ringbuffer->callback_args}; __KernelDirectMipsCall(ringbuffer->callback_addr, action, args, 3, false); } else { ERROR_LOG_REPORT(ME, "sceMpegRingbufferPut: callback_addr zero"); } return 0; } static int sceMpegGetAvcAu(u32 mpeg, u32 streamId, u32 auAddr, u32 attrAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegGetAvcAu(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, streamId, auAddr, attrAddr); return -1; } auto ringbuffer = PSPPointer::Create(ctx->mpegRingbufferAddr); if (!ringbuffer.IsValid()) { // Would have crashed before, TODO test behavior. ERROR_LOG_REPORT(ME, "sceMpegGetAvcAu(%08x, %08x, %08x, %08x): invalid ringbuffer address", mpeg, streamId, auAddr, attrAddr); return -1; } SceMpegAu avcAu; avcAu.read(auAddr); if (ringbuffer->packetsRead == 0 || ringbuffer->packetsAvail == 0) { DEBUG_LOG(ME, "ERROR_MPEG_NO_DATA=sceMpegGetAvcAu(%08x, %08x, %08x, %08x)", mpeg, streamId, auAddr, attrAddr); avcAu.pts = -1; avcAu.dts = -1; avcAu.write(auAddr); // TODO: Does this really reschedule? return hleDelayResult(ERROR_MPEG_NO_DATA, "mpeg get avc", mpegDecodeErrorDelayMs); } auto streamInfo = ctx->streamMap.find(streamId); if (streamInfo == ctx->streamMap.end()) { WARN_LOG_REPORT(ME, "sceMpegGetAvcAu: invalid video stream %08x", streamId); return -1; } if (streamInfo->second.needsReset) { avcAu.pts = 0; streamInfo->second.needsReset = false; } // esBuffer is the memory where this au data goes. We don't write the data to memory. // Instead, let's abuse it to keep track of the stream number. avcAu.esBuffer = streamInfo->second.num; /*// Wait for audio if too much ahead if (ctx->atracRegistered && (ctx->mediaengine->getVideoTimeStamp() > ctx->mediaengine->getAudioTimeStamp() + getMaxAheadTimestamp(mpegRingbuffer))) { ERROR_LOG(ME, "sceMpegGetAvcAu - video too much ahead"); // TODO: Does this really reschedule? return hleDelayResult(ERROR_MPEG_NO_DATA, "mpeg get avc", mpegDecodeErrorDelayMs); }*/ int result = 0; avcAu.pts = ctx->mediaengine->getVideoTimeStamp() + ctx->mpegFirstTimestamp; avcAu.dts = avcAu.pts - videoTimestampStep; if (ctx->mediaengine->IsVideoEnd()) { INFO_LOG(ME, "video end reach. pts: %i dts: %i", (int)avcAu.pts, (int)ctx->mediaengine->getLastTimeStamp()); ringbuffer->packetsAvail = 0; result = ERROR_MPEG_NO_DATA; } // The avcau struct may have been modified by mediaengine, write it back. avcAu.write(auAddr); // Jeanne d'Arc return 00000000 as attrAddr here and cause WriteToHardware error if (Memory::IsValidAddress(attrAddr)) { Memory::Write_U32(1, attrAddr); } DEBUG_LOG(ME, "%x=sceMpegGetAvcAu(%08x, %08x, %08x, %08x)", result, mpeg, streamId, auAddr, attrAddr); // TODO: sceMpegGetAvcAu seems to modify esSize, and delay when it's > 1000 or something. // There's definitely more to it, but ultimately it seems games should expect it to delay randomly. return hleDelayResult(result, "mpeg get avc", 100); } static u32 sceMpegFinish() { if (!isMpegInit) { WARN_LOG(ME, "sceMpegFinish(...): not initialized"); // TODO: Need to properly hook module load/unload for this to work right. //return ERROR_MPEG_NOT_YET_INIT; } else { INFO_LOG(ME, "sceMpegFinish(...)"); __VideoPmpShutdown(); } isMpegInit = false; //__MpegFinish(); return hleDelayResult(0, "mpeg finish", 250); } static u32 sceMpegQueryMemSize() { DEBUG_LOG(ME, "%i = sceMpegQueryMemSize()",MPEG_MEMSIZE); return MPEG_MEMSIZE; } static int sceMpegGetAtracAu(u32 mpeg, u32 streamId, u32 auAddr, u32 attrAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegGetAtracAu(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, streamId, auAddr, attrAddr); return -1; } auto ringbuffer = PSPPointer::Create(ctx->mpegRingbufferAddr); if (!ringbuffer.IsValid()) { // Would have crashed before, TODO test behavior. WARN_LOG(ME, "sceMpegGetAtracAu(%08x, %08x, %08x, %08x): invalid ringbuffer address", mpeg, streamId, auAddr, attrAddr); return -1; } SceMpegAu atracAu; atracAu.read(auAddr); auto streamInfo = ctx->streamMap.find(streamId); if (streamInfo != ctx->streamMap.end() && streamInfo->second.needsReset) { atracAu.pts = 0; streamInfo->second.needsReset = false; } if (streamInfo == ctx->streamMap.end()) { WARN_LOG_REPORT(ME, "sceMpegGetAtracAu: invalid audio stream %08x", streamId); // TODO: Why was this changed to not return an error? } // The audio can end earlier than the video does. if (ringbuffer->packetsAvail == 0) { DEBUG_LOG(ME, "ERROR_MPEG_NO_DATA=sceMpegGetAtracAu(%08x, %08x, %08x, %08x)", mpeg, streamId, auAddr, attrAddr); // TODO: Does this really delay? return hleDelayResult(ERROR_MPEG_NO_DATA, "mpeg get atrac", mpegDecodeErrorDelayMs); } // esBuffer is the memory where this au data goes. We don't write the data to memory. // Instead, let's abuse it to keep track of the stream number. if (streamInfo != ctx->streamMap.end()) { atracAu.esBuffer = streamInfo->second.num; } int result = 0; atracAu.pts = ctx->mediaengine->getAudioTimeStamp() + ctx->mpegFirstTimestamp; if (ctx->mediaengine->IsVideoEnd()) { INFO_LOG(ME, "video end reach. pts: %i dts: %i", (int)atracAu.pts, (int)ctx->mediaengine->getLastTimeStamp()); ringbuffer->packetsAvail = 0; result = ERROR_MPEG_NO_DATA; } if (ctx->atracRegistered && ctx->mediaengine->IsNoAudioData() && !ctx->endOfAudioReached) { WARN_LOG(ME, "Audio end reach. pts: %i dts: %i", (int)atracAu.pts, (int)ctx->mediaengine->getLastTimeStamp()); ctx->endOfAudioReached = true; result = ERROR_MPEG_NO_DATA; } atracAu.write(auAddr); // 3rd birthday return 00000000 as attrAddr here and cause WriteToHardware error if (Memory::IsValidAddress(attrAddr)) { Memory::Write_U32(0, attrAddr); } DEBUG_LOG(ME, "%x=sceMpegGetAtracAu(%08x, %08x, %08x, %08x)", result, mpeg, streamId, auAddr, attrAddr); // TODO: Not clear on exactly when this delays. return hleDelayResult(result, "mpeg get atrac", 100); } static int sceMpegQueryPcmEsSize(u32 mpeg, u32 esSizeAddr, u32 outSizeAddr) { if (!Memory::IsValidAddress(esSizeAddr) || !Memory::IsValidAddress(outSizeAddr)) { ERROR_LOG(ME, "sceMpegQueryPcmEsSize(%08x, %08x, %08x): invalid addresses", mpeg, esSizeAddr, outSizeAddr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegQueryPcmEsSize(%08x, %08x, %08x): bad mpeg handle", mpeg, esSizeAddr, outSizeAddr); return -1; } ERROR_LOG(ME, "sceMpegQueryPcmEsSize(%08x, %08x, %08x)", mpeg, esSizeAddr, outSizeAddr); Memory::Write_U32(MPEG_PCM_ES_SIZE, esSizeAddr); Memory::Write_U32(MPEG_PCM_ES_OUTPUT_SIZE, outSizeAddr); return 0; } static u32 sceMpegChangeGetAuMode(u32 mpeg, int streamUid, int mode) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegChangeGetAuMode(%08x, %i, %i): bad mpeg handle", mpeg, streamUid, mode); return ERROR_MPEG_INVALID_VALUE; } if (mode != MPEG_AU_MODE_DECODE && mode != MPEG_AU_MODE_SKIP) { ERROR_LOG(ME, "UNIMPL sceMpegChangeGetAuMode(%08x, %i, %i): bad mode", mpeg, streamUid, mode); return ERROR_MPEG_INVALID_VALUE; } auto stream = ctx->streamMap.find(streamUid); if (stream == ctx->streamMap.end()) { ERROR_LOG(ME, "UNIMPL sceMpegChangeGetAuMode(%08x, %i, %i): unknown streamID", mpeg, streamUid, mode); return ERROR_MPEG_INVALID_VALUE; } else { StreamInfo &info = stream->second; DEBUG_LOG(ME, "UNIMPL sceMpegChangeGetAuMode(%08x, %i, %i): changing type=%d", mpeg, streamUid, mode, info.type); switch (info.type) { case MPEG_AVC_STREAM: if (mode == MPEG_AU_MODE_DECODE) { ctx->ignoreAvc = false; } else if (mode == MPEG_AU_MODE_SKIP) { ctx->ignoreAvc = true; } break; case MPEG_AUDIO_STREAM: case MPEG_ATRAC_STREAM: if (mode == MPEG_AU_MODE_DECODE) { ctx->ignoreAtrac = false; } else if (mode == MPEG_AU_MODE_SKIP) { ctx->ignoreAtrac = true; } break; case MPEG_PCM_STREAM: if (mode == MPEG_AU_MODE_DECODE) { ctx->ignorePcm = false; } else if (mode == MPEG_AU_MODE_SKIP) { ctx->ignorePcm = true; } break; default: ERROR_LOG(ME, "UNIMPL sceMpegChangeGetAuMode(%08x, %i, %i): unknown streamID", mpeg, streamUid, mode); break; } } return 0; } static u32 sceMpegChangeGetAvcAuMode(u32 mpeg, u32 stream_addr, int mode) { if (!Memory::IsValidAddress(stream_addr)) { ERROR_LOG(ME, "UNIMPL sceMpegChangeGetAvcAuMode(%08x, %08x, %i): invalid addresses", mpeg, stream_addr, mode); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegChangeGetAvcAuMode(%08x, %08x, %i): bad mpeg handle", mpeg, stream_addr, mode); return -1; } ERROR_LOG_REPORT_ONCE(mpegChangeAvcAu, ME, "UNIMPL sceMpegChangeGetAvcAuMode(%08x, %08x, %i)", mpeg, stream_addr, mode); return 0; } static u32 sceMpegGetPcmAu(u32 mpeg, int streamUid, u32 auAddr, u32 attrAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegGetPcmAu(%08x, %i, %08x, %08x): bad mpeg handle", mpeg, streamUid, auAddr, attrAddr); return -1; } auto ringbuffer = PSPPointer::Create(ctx->mpegRingbufferAddr); if (!ringbuffer.IsValid()) { // Would have crashed before, TODO test behavior WARN_LOG(ME, "sceMpegGetPcmAu(%08x, %08x, %08x, %08x): invalid ringbuffer address", mpeg, streamUid, auAddr, attrAddr); return -1; } if (!Memory::IsValidAddress(streamUid)) { WARN_LOG(ME, "sceMpegGetPcmAu(%08x, %08x, %08x, %08x): didn't get a fake stream", mpeg, streamUid, auAddr, attrAddr); return ERROR_MPEG_INVALID_ADDR; } SceMpegAu atracAu; atracAu.read(auAddr); auto streamInfo = ctx->streamMap.find(streamUid); if (streamInfo == ctx->streamMap.end()) { WARN_LOG(ME, "sceMpegGetPcmAu(%08x, %08x, %08x, %08x): bad streamUid ", mpeg, streamUid, auAddr, attrAddr); return -1; } atracAu.write(auAddr); u32 attr = 1 << 7; // Sampling rate (1 = 44.1kHz). attr |= 2; // Number of channels (1 - MONO / 2 - STEREO). if (Memory::IsValidAddress(attrAddr)) Memory::Write_U32(attr, attrAddr); ERROR_LOG_REPORT_ONCE(mpegPcmAu, ME, "UNIMPL sceMpegGetPcmAu(%08x, %i, %08x, %08x)", mpeg, streamUid, auAddr, attrAddr); return 0; } static int __MpegRingbufferQueryPackNum(u32 memorySize) { return memorySize / (2048 + 104); } static int sceMpegRingbufferQueryPackNum(u32 memorySize) { DEBUG_LOG(ME, "sceMpegRingbufferQueryPackNum(%i)", memorySize); return __MpegRingbufferQueryPackNum(memorySize); } static u32 sceMpegFlushAllStream(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegFlushAllStream(%08x): bad mpeg handle", mpeg); return -1; } WARN_LOG(ME, "UNIMPL sceMpegFlushAllStream(%08x)", mpeg); ctx->isAnalyzed = false; auto ringbuffer = PSPPointer::Create(ctx->mpegRingbufferAddr); if (ringbuffer.IsValid()) { ringbuffer->packetsAvail = 0; ringbuffer->packetsRead = 0; ringbuffer->packetsWritten = 0; } return 0; } static u32 sceMpegFlushStream(u32 mpeg, int stream_addr) { if (!Memory::IsValidAddress(stream_addr)) { ERROR_LOG(ME, "UNIMPL sceMpegFlushStream(%08x, %i): invalid addresses", mpeg , stream_addr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegFlushStream(%08x, %i): bad mpeg handle", mpeg , stream_addr); return -1; } ERROR_LOG(ME, "UNIMPL sceMpegFlushStream(%08x, %i)", mpeg , stream_addr); //__MpegFinish(); return 0; } static u32 sceMpegAvcCopyYCbCr(u32 mpeg, u32 sourceAddr, u32 YCbCrAddr) { if (!Memory::IsValidAddress(sourceAddr) || !Memory::IsValidAddress(YCbCrAddr)) { ERROR_LOG(ME, "UNIMPL sceMpegAvcCopyYCbCr(%08x, %08x, %08x): invalid addresses", mpeg, sourceAddr, YCbCrAddr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegAvcCopyYCbCr(%08x, %08x, %08x): bad mpeg handle", mpeg, sourceAddr, YCbCrAddr); return -1; } ERROR_LOG(ME, "UNIMPL sceMpegAvcCopyYCbCr(%08x, %08x, %08x)", mpeg, sourceAddr, YCbCrAddr); return 0; } static u32 sceMpegAtracDecode(u32 mpeg, u32 auAddr, u32 bufferAddr, int init) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegAtracDecode(%08x, %08x, %08x, %i): bad mpeg handle", mpeg, auAddr, bufferAddr, init); return -1; } DEBUG_LOG(ME, "sceMpegAtracDecode(%08x, %08x, %08x, %i)", mpeg, auAddr, bufferAddr, init); SceMpegAu atracAu; atracAu.read(auAddr); // We kept track of the stream number here in sceMpegGetAtracAu(). ctx->mediaengine->setAudioStream(atracAu.esBuffer); Memory::Memset(bufferAddr, 0, MPEG_ATRAC_ES_OUTPUT_SIZE); ctx->mediaengine->getAudioSamples(bufferAddr); atracAu.pts = ctx->mediaengine->getAudioTimeStamp() + ctx->mpegFirstTimestamp; atracAu.write(auAddr); return hleDelayResult(0, "mpeg atrac decode", atracDecodeDelayMs); //hleEatMicro(4000); //return hleDelayResult(0, "mpeg atrac decode", 200); } // YCbCr -> RGB color space conversion static u32 sceMpegAvcCsc(u32 mpeg, u32 sourceAddr, u32 rangeAddr, int frameWidth, u32 destAddr) { if (!Memory::IsValidAddress(sourceAddr) || !Memory::IsValidAddress(rangeAddr) || !Memory::IsValidAddress(destAddr)) { ERROR_LOG(ME, "sceMpegAvcCsc(%08x, %08x, %08x, %i, %08x): invalid addresses", mpeg, sourceAddr, rangeAddr, frameWidth, destAddr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegAvcCsc(%08x, %08x, %08x, %i, %08x): bad mpeg handle", mpeg, sourceAddr, rangeAddr, frameWidth, destAddr); return -1; } DEBUG_LOG(ME, "sceMpegAvcCsc(%08x, %08x, %08x, %i, %08x)", mpeg, sourceAddr, rangeAddr, frameWidth, destAddr); int x = Memory::Read_U32(rangeAddr); int y = Memory::Read_U32(rangeAddr + 4); int width = Memory::Read_U32(rangeAddr + 8); int height = Memory::Read_U32(rangeAddr + 12); int destSize = ctx->mediaengine->writeVideoImageWithRange(destAddr, frameWidth, ctx->videoPixelMode, x, y, width, height); gpu->InvalidateCache(destAddr, destSize, GPU_INVALIDATE_SAFE); // Do not use avcDecodeDelayMs 's value // Will cause video 's screen dislocation in Bleach heat of soul 6 // https://github.com/hrydgard/ppsspp/issues/5535 // If do not use DelayResult,Wil cause flickering in Dengeki no Pilot: Tenkuu no Kizuna // https://github.com/hrydgard/ppsspp/issues/7549 return hleDelayResult(0, "mpeg avc csc", avcCscDelayMs); } static u32 sceMpegRingbufferDestruct(u32 ringbufferAddr) { DEBUG_LOG(ME, "sceMpegRingbufferDestruct(%08x)", ringbufferAddr); // Apparently, does nothing. return 0; } static u32 sceMpegAvcInitYCbCr(u32 mpeg, int mode, int width, int height, u32 ycbcr_addr) { if (!Memory::IsValidAddress(ycbcr_addr)) { ERROR_LOG(ME, "UNIMPL sceMpegAvcInitYCbCr(%08x, %i, %i, %i, %08x): invalid addresses", mpeg, mode, width, height, ycbcr_addr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegAvcInitYCbCr(%08x, %i, %i, %i, %08x): bad mpeg handle", mpeg, mode, width, height, ycbcr_addr); return -1; } ERROR_LOG(ME, "UNIMPL sceMpegAvcInitYCbCr(%08x, %i, %i, %i, %08x)", mpeg, mode, width, height, ycbcr_addr); return 0; } static int sceMpegAvcQueryYCbCrSize(u32 mpeg, u32 mode, u32 width, u32 height, u32 resultAddr) { if ((width & 15) != 0 || (height & 15) != 0 || height > 272 || width > 480) { ERROR_LOG(ME, "sceMpegAvcQueryYCbCrSize: bad w/h %i x %i", width, height); return ERROR_MPEG_INVALID_VALUE; } DEBUG_LOG(ME, "sceMpegAvcQueryYCbCrSize(%08x, %i, %i, %i, %08x)", mpeg, mode, width, height, resultAddr); int size = (width / 2) * (height / 2) * 6 + 128; Memory::Write_U32(size, resultAddr); return 0; } static u32 sceMpegQueryUserdataEsSize(u32 mpeg, u32 esSizeAddr, u32 outSizeAddr) { if (!Memory::IsValidAddress(esSizeAddr) || !Memory::IsValidAddress(outSizeAddr)) { ERROR_LOG(ME, "sceMpegQueryUserdataEsSize(%08x, %08x, %08x): invalid addresses", mpeg, esSizeAddr, outSizeAddr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegQueryUserdataEsSize(%08x, %08x, %08x): bad mpeg handle", mpeg, esSizeAddr, outSizeAddr); return -1; } DEBUG_LOG(ME, "sceMpegQueryUserdataEsSize(%08x, %08x, %08x)", mpeg, esSizeAddr, outSizeAddr); Memory::Write_U32(MPEG_DATA_ES_SIZE, esSizeAddr); Memory::Write_U32(MPEG_DATA_ES_OUTPUT_SIZE, outSizeAddr); return 0; } static u32 sceMpegAvcResourceGetAvcDecTopAddr(u32 mpeg) { ERROR_LOG(ME, "UNIMPL sceMpegAvcResourceGetAvcDecTopAddr(%08x)", mpeg); // it's just a random address return 0x12345678; } static u32 sceMpegAvcResourceFinish(u32 mpeg) { DEBUG_LOG(ME,"UNIMPL sceMpegAvcResourceFinish(%08x)", mpeg); return 0; } static u32 sceMpegAvcResourceGetAvcEsBuf(u32 mpeg) { ERROR_LOG_REPORT_ONCE(mpegResourceEsBuf, ME, "UNIMPL sceMpegAvcResourceGetAvcEsBuf(%08x)", mpeg); return 0; } static u32 sceMpegAvcResourceInit(u32 mpeg) { if (mpeg != 1) { return ERROR_MPEG_INVALID_VALUE; } ERROR_LOG(ME, "UNIMPL sceMpegAvcResourceInit(%08x)", mpeg); return 0; } static u32 convertABGRToYCbCr(u32 abgr) { //see http://en.wikipedia.org/wiki/Yuv#Y.27UV444_to_RGB888_conversion for more information. u8 r = (abgr >> 0) & 0xFF; u8 g = (abgr >> 8) & 0xFF; u8 b = (abgr >> 16) & 0xFF; int y = 0.299f * r + 0.587f * g + 0.114f * b + 0; int cb = -0.169f * r - 0.331f * g + 0.499f * b + 128.0f; int cr = 0.499f * r - 0.418f * g - 0.0813f * b + 128.0f; // check yCbCr value if ( y > 0xFF) y = 0xFF; if ( y < 0) y = 0; if (cb > 0xFF) cb = 0xFF; if (cb < 0) cb = 0; if (cr > 0xFF) cr = 0xFF; if (cr < 0) cr = 0; return (y << 16) | (cb << 8) | cr; } static int __MpegAvcConvertToYuv420(const void *data, u32 bufferOutputAddr, int width, int height) { u32 *imageBuffer = (u32*)data; int sizeY = width * height; int sizeCb = sizeY >> 2; u8 *Y = (u8*)Memory::GetPointer(bufferOutputAddr); u8 *Cb = Y + sizeY; u8 *Cr = Cb + sizeCb; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; x += 4) { u32 abgr0 = imageBuffer[x + 0]; u32 abgr1 = imageBuffer[x + 1]; u32 abgr2 = imageBuffer[x + 2]; u32 abgr3 = imageBuffer[x + 3]; u32 yCbCr0 = convertABGRToYCbCr(abgr0); u32 yCbCr1 = convertABGRToYCbCr(abgr1); u32 yCbCr2 = convertABGRToYCbCr(abgr2); u32 yCbCr3 = convertABGRToYCbCr(abgr3); Y[x + 0] = (yCbCr0 >> 16) & 0xFF; Y[x + 1] = (yCbCr1 >> 16) & 0xFF; Y[x + 2] = (yCbCr2 >> 16) & 0xFF; Y[x + 3] = (yCbCr3 >> 16) & 0xFF; *Cb++ = (yCbCr0 >> 8) & 0xFF; *Cr++ = yCbCr0 & 0xFF; } imageBuffer += width; Y += width ; } return (width << 16) | height; } static int sceMpegAvcConvertToYuv420(u32 mpeg, u32 bufferOutputAddr, u32 unknown1, int unknown2) { if (!Memory::IsValidAddress(bufferOutputAddr)) { ERROR_LOG(ME, "sceMpegAvcConvertToYuv420(%08x, %08x, %08x, %08x): invalid addresses", mpeg, bufferOutputAddr, unknown1, unknown2); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegAvcConvertToYuv420(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, bufferOutputAddr, unknown1, unknown2); return -1; } if (ctx->mediaengine->m_buffer == 0){ WARN_LOG(ME, "sceMpegAvcConvertToYuv420(%08x, %08x, %08x, %08x): m_buffer is zero ", mpeg, bufferOutputAddr, unknown1, unknown2); return ERROR_MPEG_AVC_INVALID_VALUE; } DEBUG_LOG(ME, "sceMpegAvcConvertToYuv420(%08x, %08x, %08x, %08x)", mpeg, bufferOutputAddr, unknown1, unknown2); const u8 *data = ctx->mediaengine->getFrameImage(); int width = ctx->mediaengine->m_desWidth; int height = ctx->mediaengine->m_desHeight; if (data) { __MpegAvcConvertToYuv420(data, bufferOutputAddr, width, height); } return 0; } static int sceMpegGetUserdataAu(u32 mpeg, u32 streamUid, u32 auAddr, u32 resultAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "sceMpegGetUserdataAu(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, streamUid, auAddr, resultAddr); return -1; } DEBUG_LOG(ME, "sceMpegGetUserdataAu(%08x, %08x, %08x, %08x)", mpeg, streamUid, auAddr, resultAddr); // TODO: Are these at all right? Seen in Phantasy Star Portable 2. Memory::Write_U32(0, resultAddr); Memory::Write_U32(0, resultAddr + 4); // We currently can't demux userdata so this seems like the best thing to return in the meantime.. // Then we probably shouldn't do the above writes? but it works... return ERROR_MPEG_NO_DATA; } static u32 sceMpegNextAvcRpAu(u32 mpeg, u32 streamUid) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegNextAvcRpAu(%08x, %08x): bad mpeg handle", mpeg, streamUid); return -1; } ERROR_LOG_REPORT(ME, "UNIMPL sceMpegNextAvcRpAu(%08x, %08x)", mpeg, streamUid); return 0; } static u32 sceMpegGetAvcNalAu(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegGetAvcNalAu(%08x): bad mpeg handle", mpeg); return -1; } ERROR_LOG_REPORT(ME, "UNIMPL sceMpegGetAvcNalAu(%08x)", mpeg); return 0; } static u32 sceMpegAvcDecodeDetailIndex(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegAvcDecodeDetailIndex(%08x): bad mpeg handle", mpeg); return -1; } ERROR_LOG_REPORT(ME, "UNIMPL sceMpegAvcDecodeDetailIndex(%08x)", mpeg); return 0; } static u32 sceMpegAvcDecodeDetail2(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegAvcDecodeDetail2(%08x): bad mpeg handle", mpeg); return -1; } ERROR_LOG_REPORT(ME, "UNIMPL sceMpegAvcDecodeDetail2(%08x)", mpeg); return 0; } static u32 sceMpegGetAvcEsAu(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegGetAvcEsAu(%08x): bad mpeg handle", mpeg); return -1; } ERROR_LOG_REPORT(ME, "UNIMPL sceMpegGetAvcEsAu(%08x)", mpeg); return 0; } static u32 sceMpegAvcCscInfo(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegAvcCscInfo(%08x): bad mpeg handle", mpeg); return -1; } ERROR_LOG_REPORT(ME, "UNIMPL sceMpegAvcCscInfo(%08x)", mpeg); return 0; } static u32 sceMpegAvcCscMode(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegAvcCscMode(%08x): bad mpeg handle", mpeg); return -1; } ERROR_LOG_REPORT(ME, "UNIMPL sceMpegAvcCscMode(%08x)", mpeg); return 0; } static u32 sceMpegFlushAu(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(ME, "UNIMPL sceMpegFlushAu(%08x): bad mpeg handle", mpeg); return -1; } ERROR_LOG_REPORT(ME, "UNIMPL sceMpegFlushAu(%08x)", mpeg); return 0; } const HLEFunction sceMpeg[] = { {0XE1CE83A7, &WrapI_UUUU, "sceMpegGetAtracAu", 'i', "xxxx" }, {0XFE246728, &WrapI_UUUU, "sceMpegGetAvcAu", 'i', "xxxx" }, {0XD8C5F121, &WrapU_UUUUUUU, "sceMpegCreate", 'x', "xxxxxxx"}, {0XF8DCB679, &WrapI_UUU, "sceMpegQueryAtracEsSize", 'i', "xxx" }, {0XC132E22F, &WrapU_V, "sceMpegQueryMemSize", 'x', "" }, {0X21FF80E4, &WrapI_UUU, "sceMpegQueryStreamOffset", 'i', "xxx" }, {0X611E9E11, &WrapU_UU, "sceMpegQueryStreamSize", 'x', "xx" }, {0X42560F23, &WrapI_UUU, "sceMpegRegistStream", 'i', "xxx" }, {0X591A4AA2, &WrapU_UI, "sceMpegUnRegistStream", 'x', "xi" }, {0X707B7629, &WrapU_U, "sceMpegFlushAllStream", 'x', "x" }, {0X500F0429, &WrapU_UI, "sceMpegFlushStream", 'x', "xi" }, {0XA780CF7E, &WrapI_U, "sceMpegMallocAvcEsBuf", 'i', "x" }, {0XCEB870B1, &WrapI_UI, "sceMpegFreeAvcEsBuf", 'i', "xi" }, {0X167AFD9E, &WrapI_UUU, "sceMpegInitAu", 'i', "xxx" }, {0X682A619B, &WrapU_V, "sceMpegInit", 'x', "" }, {0X606A4649, &WrapI_U, "sceMpegDelete", 'i', "x" }, {0X874624D6, &WrapU_V, "sceMpegFinish", 'x', "" }, {0X800C44DF, &WrapU_UUUI, "sceMpegAtracDecode", 'x', "xxxi" }, {0X0E3C2E9D, &WrapU_UUUUU, "sceMpegAvcDecode", 'x', "xxxxx" }, {0X740FCCD1, &WrapU_UUUU, "sceMpegAvcDecodeStop", 'x', "xxxx" }, {0X4571CC64, &WrapU_U, "sceMpegAvcDecodeFlush", 'x', "x" }, {0X0F6C18D7, &WrapI_UU, "sceMpegAvcDecodeDetail", 'i', "xx" }, {0XA11C7026, &WrapI_UU, "sceMpegAvcDecodeMode", 'i', "xx" }, {0X37295ED8, &WrapU_UUUUUU, "sceMpegRingbufferConstruct", 'x', "xxxxxx" }, {0X13407F13, &WrapU_U, "sceMpegRingbufferDestruct", 'x', "x" }, {0XB240A59E, &WrapU_UUU, "sceMpegRingbufferPut", 'x', "xxx" }, {0XB5F6DC87, &WrapI_U, "sceMpegRingbufferAvailableSize", 'i', "x" }, {0XD7A29F46, &WrapU_I, "sceMpegRingbufferQueryMemSize", 'x', "i" }, {0X769BEBB6, &WrapI_U, "sceMpegRingbufferQueryPackNum", 'i', "x" }, {0X211A057C, &WrapI_UUUUU, "sceMpegAvcQueryYCbCrSize", 'i', "xxxxx" }, {0XF0EB1125, &WrapI_UUUU, "sceMpegAvcDecodeYCbCr", 'i', "xxxx" }, {0XF2930C9C, &WrapU_UUU, "sceMpegAvcDecodeStopYCbCr", 'x', "xxx" }, {0X67179B1B, &WrapU_UIIIU, "sceMpegAvcInitYCbCr", 'x', "xiiix" }, {0X0558B075, &WrapU_UUU, "sceMpegAvcCopyYCbCr", 'x', "xxx" }, {0X31BD0272, &WrapU_UUUIU, "sceMpegAvcCsc", 'x', "xxxix" }, {0X9DCFB7EA, &WrapU_UII, "sceMpegChangeGetAuMode", 'x', "xii" }, {0X8C1E027D, &WrapU_UIUU, "sceMpegGetPcmAu", 'x', "xixx" }, {0XC02CF6B5, &WrapI_UUU, "sceMpegQueryPcmEsSize", 'i', "xxx" }, {0XC45C99CC, &WrapU_UUU, "sceMpegQueryUserdataEsSize", 'x', "xxx" }, {0X234586AE, &WrapU_UUI, "sceMpegChangeGetAvcAuMode", 'x', "xxi" }, {0X63B9536A, &WrapU_U, "sceMpegAvcResourceGetAvcDecTopAddr", 'x', "x" }, {0X8160A2FE, &WrapU_U, "sceMpegAvcResourceFinish", 'x', "x" }, {0XAF26BB01, &WrapU_U, "sceMpegAvcResourceGetAvcEsBuf", 'x', "x" }, {0XFCBDB5AD, &WrapU_U, "sceMpegAvcResourceInit", 'x', "x" }, {0XF5E7EA31, &WrapI_UUUI, "sceMpegAvcConvertToYuv420", 'i', "xxxi" }, {0X01977054, &WrapI_UUUU, "sceMpegGetUserdataAu", 'i', "xxxx" }, {0X3C37A7A6, &WrapU_UU, "sceMpegNextAvcRpAu", 'x', "xx" }, {0X11F95CF1, &WrapU_U, "sceMpegGetAvcNalAu", 'x', "x" }, {0XAB0E9556, &WrapU_U, "sceMpegAvcDecodeDetailIndex", 'x', "x" }, {0XCF3547A2, &WrapU_U, "sceMpegAvcDecodeDetail2", 'x', "x" }, {0X921FCCCF, &WrapU_U, "sceMpegGetAvcEsAu", 'x', "x" }, {0XE95838F6, &WrapU_U, "sceMpegAvcCscInfo", 'x', "x" }, {0XD1CE4950, &WrapU_U, "sceMpegAvcCscMode", 'x', "x" }, {0XDBB60658, &WrapU_U, "sceMpegFlushAu", 'x', "x" }, {0XD4DD6E75, nullptr, "sceMpeg_D4DD6E75", '?', "" }, {0X11CAB459, nullptr, "sceMpeg_11CAB459", '?', "" }, {0XC345DED2, nullptr, "sceMpeg_C345DED2", '?', "" }, {0XB27711A8, nullptr, "sceMpeg_B27711A8", '?', "" }, {0X988E9E12, nullptr, "sceMpeg_988E9E12", '?', "" }, }; void Register_sceMpeg() { RegisterModule("sceMpeg", ARRAY_SIZE(sceMpeg), sceMpeg); } // This function is currently only been used for PMP videos // p pointing to a SceMpegLLI structure consists of video frame blocks. static u32 sceMpegbase_BEA18F91(u32 p) { pmp_videoSource = p; pmp_nBlocks = 0; SceMpegLLI lli; while (1){ Memory::ReadStruct(p, &lli); pmp_nBlocks++; // lli.Next ==0 for last block if (lli.Next == 0){ break; } p = p + sizeof(SceMpegLLI); } DEBUG_LOG(ME, "sceMpegbase_BEA18F91(%08x), received %d block(s)", pmp_videoSource, pmp_nBlocks); return 0; } const HLEFunction sceMpegbase[] = { {0XBEA18F91, &WrapU_U, "sceMpegbase_BEA18F91", 'x', "x" }, {0X492B5E4B, nullptr, "sceMpegBaseCscInit", '?', "" }, {0X0530BE4E, nullptr, "sceMpegbase_0530BE4E", '?', "" }, {0X91929A21, nullptr, "sceMpegBaseCscAvc", '?', "" }, {0X304882E1, nullptr, "sceMpegBaseCscAvcRange", '?', "" }, {0X7AC0321A, nullptr, "sceMpegBaseYCrCbCopy", '?', "" } }; void Register_sceMpegbase() { RegisterModule("sceMpegbase", ARRAY_SIZE(sceMpegbase), sceMpegbase); };