ppsspp/Core/HLE/sceMpeg.cpp
Unknown W. Brackets f60aef2e16 Add a more specific interface for video frames.
Since we know more about them.  No functional change yet.
2016-01-17 12:57:26 -08:00

2279 lines
75 KiB
C++

// 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 <map>
#include <algorithm>
#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<AVFrame*> pmp_queue; //list of pmp video frames have been decoded and will be played
static std::list<u32> 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<u32, StreamInfo> 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<u32, StreamInfo>(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<u32, MpegContext *> 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<SceMpegRingBuffer>::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<SceMpegRingBuffer>::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<u32, MpegContext *>::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<SceMpegRingBuffer>::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<SceMpegRingBuffer>::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<SceMpegRingBuffer> 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<SceMpegRingBuffer>::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->NotifyVideoUpload(buffer, bufferSize, frameWidth, ctx->videoPixelMode);
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->NotifyVideoUpload(buffer, bufferSize, frameWidth, ctx->videoPixelMode);
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<SceMpegRingBuffer>::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.
// Default to 0, since we stuff the stream id in here. Technically, we shouldn't.
// TODO: Do something better to track the AU data. This used to be bufferAddr.
sceAu.esBuffer = 0;
sceAu.esSize = MPEG_AVC_ES_SIZE;
sceAu.dts = 0;
sceAu.pts = 0;
sceAu.write(auPointer);
} else {
// This esbuffer has been left as Atrac.
// Default to 0, since we stuff the stream id in here. Technically, we shouldn't.
// TODO: Do something better to track the AU data. This used to be bufferAddr.
sceAu.esBuffer = 0;
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<SceMpegRingBuffer>::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<SceMpegRingBuffer>::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<SceMpegRingBuffer>::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<SceMpegRingBuffer>::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<SceMpegRingBuffer>::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;
// TODO: Is this correct?
if (!ctx->mediaengine->IsNoAudioData()) {
WARN_LOG_REPORT(ME, "Video end without audio end, potentially skipping some audio?");
}
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;
}
if (ctx->mediaengine->IsNoAudioData()) {
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<SceMpegRingBuffer>::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<SceMpegRingBuffer>::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->NotifyVideoUpload(destAddr, destSize, frameWidth, ctx->videoPixelMode);
// 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>, "sceMpegGetAtracAu", 'i', "xxxx" },
{0XFE246728, &WrapI_UUUU<sceMpegGetAvcAu>, "sceMpegGetAvcAu", 'i', "xxxx" },
{0XD8C5F121, &WrapU_UUUUUUU<sceMpegCreate>, "sceMpegCreate", 'x', "xxxxxxx"},
{0XF8DCB679, &WrapI_UUU<sceMpegQueryAtracEsSize>, "sceMpegQueryAtracEsSize", 'i', "xxx" },
{0XC132E22F, &WrapU_V<sceMpegQueryMemSize>, "sceMpegQueryMemSize", 'x', "" },
{0X21FF80E4, &WrapI_UUU<sceMpegQueryStreamOffset>, "sceMpegQueryStreamOffset", 'i', "xxx" },
{0X611E9E11, &WrapU_UU<sceMpegQueryStreamSize>, "sceMpegQueryStreamSize", 'x', "xx" },
{0X42560F23, &WrapI_UUU<sceMpegRegistStream>, "sceMpegRegistStream", 'i', "xxx" },
{0X591A4AA2, &WrapU_UI<sceMpegUnRegistStream>, "sceMpegUnRegistStream", 'x', "xi" },
{0X707B7629, &WrapU_U<sceMpegFlushAllStream>, "sceMpegFlushAllStream", 'x', "x" },
{0X500F0429, &WrapU_UI<sceMpegFlushStream>, "sceMpegFlushStream", 'x', "xi" },
{0XA780CF7E, &WrapI_U<sceMpegMallocAvcEsBuf>, "sceMpegMallocAvcEsBuf", 'i', "x" },
{0XCEB870B1, &WrapI_UI<sceMpegFreeAvcEsBuf>, "sceMpegFreeAvcEsBuf", 'i', "xi" },
{0X167AFD9E, &WrapI_UUU<sceMpegInitAu>, "sceMpegInitAu", 'i', "xxx" },
{0X682A619B, &WrapU_V<sceMpegInit>, "sceMpegInit", 'x', "" },
{0X606A4649, &WrapI_U<sceMpegDelete>, "sceMpegDelete", 'i', "x" },
{0X874624D6, &WrapU_V<sceMpegFinish>, "sceMpegFinish", 'x', "" },
{0X800C44DF, &WrapU_UUUI<sceMpegAtracDecode>, "sceMpegAtracDecode", 'x', "xxxi" },
{0X0E3C2E9D, &WrapU_UUUUU<sceMpegAvcDecode>, "sceMpegAvcDecode", 'x', "xxxxx" },
{0X740FCCD1, &WrapU_UUUU<sceMpegAvcDecodeStop>, "sceMpegAvcDecodeStop", 'x', "xxxx" },
{0X4571CC64, &WrapU_U<sceMpegAvcDecodeFlush>, "sceMpegAvcDecodeFlush", 'x', "x" },
{0X0F6C18D7, &WrapI_UU<sceMpegAvcDecodeDetail>, "sceMpegAvcDecodeDetail", 'i', "xx" },
{0XA11C7026, &WrapI_UU<sceMpegAvcDecodeMode>, "sceMpegAvcDecodeMode", 'i', "xx" },
{0X37295ED8, &WrapU_UUUUUU<sceMpegRingbufferConstruct>, "sceMpegRingbufferConstruct", 'x', "xxxxxx" },
{0X13407F13, &WrapU_U<sceMpegRingbufferDestruct>, "sceMpegRingbufferDestruct", 'x', "x" },
{0XB240A59E, &WrapU_UUU<sceMpegRingbufferPut>, "sceMpegRingbufferPut", 'x', "xxx" },
{0XB5F6DC87, &WrapI_U<sceMpegRingbufferAvailableSize>, "sceMpegRingbufferAvailableSize", 'i', "x" },
{0XD7A29F46, &WrapU_I<sceMpegRingbufferQueryMemSize>, "sceMpegRingbufferQueryMemSize", 'x', "i" },
{0X769BEBB6, &WrapI_U<sceMpegRingbufferQueryPackNum>, "sceMpegRingbufferQueryPackNum", 'i', "x" },
{0X211A057C, &WrapI_UUUUU<sceMpegAvcQueryYCbCrSize>, "sceMpegAvcQueryYCbCrSize", 'i', "xxxxx" },
{0XF0EB1125, &WrapI_UUUU<sceMpegAvcDecodeYCbCr>, "sceMpegAvcDecodeYCbCr", 'i', "xxxx" },
{0XF2930C9C, &WrapU_UUU<sceMpegAvcDecodeStopYCbCr>, "sceMpegAvcDecodeStopYCbCr", 'x', "xxx" },
{0X67179B1B, &WrapU_UIIIU<sceMpegAvcInitYCbCr>, "sceMpegAvcInitYCbCr", 'x', "xiiix" },
{0X0558B075, &WrapU_UUU<sceMpegAvcCopyYCbCr>, "sceMpegAvcCopyYCbCr", 'x', "xxx" },
{0X31BD0272, &WrapU_UUUIU<sceMpegAvcCsc>, "sceMpegAvcCsc", 'x', "xxxix" },
{0X9DCFB7EA, &WrapU_UII<sceMpegChangeGetAuMode>, "sceMpegChangeGetAuMode", 'x', "xii" },
{0X8C1E027D, &WrapU_UIUU<sceMpegGetPcmAu>, "sceMpegGetPcmAu", 'x', "xixx" },
{0XC02CF6B5, &WrapI_UUU<sceMpegQueryPcmEsSize>, "sceMpegQueryPcmEsSize", 'i', "xxx" },
{0XC45C99CC, &WrapU_UUU<sceMpegQueryUserdataEsSize>, "sceMpegQueryUserdataEsSize", 'x', "xxx" },
{0X234586AE, &WrapU_UUI<sceMpegChangeGetAvcAuMode>, "sceMpegChangeGetAvcAuMode", 'x', "xxi" },
{0X63B9536A, &WrapU_U<sceMpegAvcResourceGetAvcDecTopAddr>, "sceMpegAvcResourceGetAvcDecTopAddr", 'x', "x" },
{0X8160A2FE, &WrapU_U<sceMpegAvcResourceFinish>, "sceMpegAvcResourceFinish", 'x', "x" },
{0XAF26BB01, &WrapU_U<sceMpegAvcResourceGetAvcEsBuf>, "sceMpegAvcResourceGetAvcEsBuf", 'x', "x" },
{0XFCBDB5AD, &WrapU_U<sceMpegAvcResourceInit>, "sceMpegAvcResourceInit", 'x', "x" },
{0XF5E7EA31, &WrapI_UUUI<sceMpegAvcConvertToYuv420>, "sceMpegAvcConvertToYuv420", 'i', "xxxi" },
{0X01977054, &WrapI_UUUU<sceMpegGetUserdataAu>, "sceMpegGetUserdataAu", 'i', "xxxx" },
{0X3C37A7A6, &WrapU_UU<sceMpegNextAvcRpAu>, "sceMpegNextAvcRpAu", 'x', "xx" },
{0X11F95CF1, &WrapU_U<sceMpegGetAvcNalAu>, "sceMpegGetAvcNalAu", 'x', "x" },
{0XAB0E9556, &WrapU_U<sceMpegAvcDecodeDetailIndex>, "sceMpegAvcDecodeDetailIndex", 'x', "x" },
{0XCF3547A2, &WrapU_U<sceMpegAvcDecodeDetail2>, "sceMpegAvcDecodeDetail2", 'x', "x" },
{0X921FCCCF, &WrapU_U<sceMpegGetAvcEsAu>, "sceMpegGetAvcEsAu", 'x', "x" },
{0XE95838F6, &WrapU_U<sceMpegAvcCscInfo>, "sceMpegAvcCscInfo", 'x', "x" },
{0XD1CE4950, &WrapU_U<sceMpegAvcCscMode>, "sceMpegAvcCscMode", 'x', "x" },
{0XDBB60658, &WrapU_U<sceMpegFlushAu>, "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>, "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);
};