Merge pull request #22306 from hrydgard/assorted-fixes

Various fixes to sceMpeg LLE and sceMp3 LLE
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-09-18 10:11:22 -06:00
commit 21c7312486
8 files changed
+122 -42

No files matched your search

+4 -23
View File
@@ -515,27 +515,8 @@ void LogManager::StdioLog(const LogMessage &message) {
}
void PrintfLog(const LogMessage &message) {
const char *category = message.log;
switch (message.level) {
case LogLevel::LVERBOSE:
fprintf(stderr, "V %s: %s", category, message.msg.c_str());
break;
case LogLevel::LDEBUG:
fprintf(stderr, "D %s: %s", category, message.msg.c_str());
break;
case LogLevel::LINFO:
fprintf(stderr, "I %s: %s", category, message.msg.c_str());
break;
case LogLevel::LERROR:
fprintf(stderr, "E %s: %s", category, message.msg.c_str());
break;
case LogLevel::LWARNING:
fprintf(stderr, "W %s: %s", category, message.msg.c_str());
break;
case LogLevel::LNOTICE:
default:
fprintf(stderr, "N %s: %s", category, message.msg.c_str());
break;
}
// Same shape as the stdio and file outputs the other builds use. It used to be its own
// shorter format, which meant a pattern that matched a log from the app quietly matched
// nothing in one from headless.
fprintf(stderr, "%s %s %s", message.timestamp, message.header, message.msg.c_str());
}
+44 -11
View File
@@ -21,6 +21,7 @@
#include "Core/HLE/HLE.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/sceAudiocodec.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/HLE/ErrorCodes.h"
#include "Core/MemMap.h"
#include "Core/Reporting.h"
@@ -397,7 +398,11 @@ static int sceAudiocodecDecode(u32 ctxPtr, int codec) {
}
ctx->srcBytesRead = inDataConsumed + headerBytes;
ctx->dstSamplesWritten = outSamples;
// In bytes, not samples. sceAudiocodecGetOutputBytes describes the same quantity in bytes
// (0x1200 for MPEG1 MP3), and libmp3.prx takes this as the length of the PCM to hand on -
// reporting the sample count instead gave it a quarter of every frame, which played back
// fast and metallic. The decoder always writes stereo 16-bit, whatever the source is.
ctx->dstBytesWritten = outSamples * 2 * (int)sizeof(int16_t);
}
return hleLogDebug(Log::ME, 0, "codec %s sampleRate: %d bytesPerFrame: %d channels: %d", GetCodecName(codec), sampleRate, bytesPerFrame, channels);
}
@@ -414,19 +419,47 @@ static int sceAudiocodecGetInfo(u32 ctxPtr, int codec) {
// Write some expected values.
switch (codec) {
case PSP_CODEC_MP3:
// When this is called, the caller has written:
// * inptr
// * outptr
// * fmt.mp3.maxFrameBytes = 0x5A1
// Our response is written to a bunch of fields, but I really don't know much
// about what the values are - this is handled internally in the ME.
{
// The caller has written inBuf, outBuf and maxFrameBytes, and left version at the 9999
// sceAudiocodecInit puts there to mean "not known yet". Filling that in is the point of
// this call - libmp3.prx reads it straight back out, and leaving it at 9999 is what made
// Meruru no Atelier Plus go silent: it got this far and then stopped without ever asking
// for a decode.
//
// The hardware reads these off the frame, so read them off the frame. Apart from the
// version index, which has its own numbering, they are the raw MPEG header fields.
ctx->fmt.mp3.unk3c = 3;
ctx->fmt.mp3.bitrateIndex = 9;
ctx->fmt.mp3.sampleRateIndex = 0;
ctx->fmt.mp3.unk60 = 1;
ctx->fmt.mp3.channelConfig = 1;
// Header version bits are 0 = MPEG2.5, 2 = MPEG2, 3 = MPEG1 (1 is reserved); the field
// wants 0 = MPEG2, 1 = MPEG1, 2 = MPEG2.5.
static const int versionFromHeader[4] = { 2, -1, 0, 1 };
const u8 *header = Memory::IsValidRange(ctx->inBuf, 4) ? Memory::GetPointerUnchecked(ctx->inBuf) : nullptr;
const bool haveFrame = header && header[0] == 0xFF && (header[1] & 0xE0) == 0xE0 &&
versionFromHeader[(header[1] >> 3) & 3] >= 0;
if (haveFrame) {
ctx->fmt.mp3.version = versionFromHeader[(header[1] >> 3) & 3];
ctx->fmt.mp3.bitrateIndex = (header[2] >> 4) & 0x0F;
ctx->fmt.mp3.sampleRateIndex = (header[2] >> 2) & 0x03;
ctx->fmt.mp3.channelConfig = (header[3] >> 6) & 0x03;
INFO_LOG(Log::ME, "GetInfo MP3: sdk=%08x version=%d bitrateIdx=%d sampleRateIdx=%d channelConfig=%d (hdr %02x %02x %02x %02x)",
sceKernelGetCompiledSdkVersion(), (int)ctx->fmt.mp3.version, (int)ctx->fmt.mp3.bitrateIndex, (int)ctx->fmt.mp3.sampleRateIndex,
(int)ctx->fmt.mp3.channelConfig, header[0], header[1], header[2], header[3]);
} else {
// Nothing readable to look at. Claim 128kbps 44.1kHz stereo, as this used to
// unconditionally - but do set the version, since 9999 stops the caller dead.
// Worth hearing about: everything the caller does with the stream follows from these,
// so if a game ever lands here its audio will be wrong in a way that starts right here.
WARN_LOG(Log::ME, "sceAudiocodecGetInfo: no MP3 frame at inBuf %08x, guessing 128kbps 44.1kHz stereo",
ctx->inBuf);
ctx->fmt.mp3.version = 1;
ctx->fmt.mp3.bitrateIndex = 9;
ctx->fmt.mp3.sampleRateIndex = 0;
ctx->fmt.mp3.channelConfig = 1;
}
break;
}
}
return hleLogInfo(Log::ME, 0, "codec=%s", GetCodecName(codec));
}
@@ -471,7 +504,7 @@ static int sceAudiocodecCheckNeedMem(u32 ctxPtr, int codec) {
ctx->err = 0;
ctx->magic = 0x5100601;
return hleLogWarning(Log::ME, 0, "%s", GetCodecName(codec));
return hleLogInfo(Log::ME, 0, "%s: %x", GetCodecName(codec), ctx->neededMem);
}
static int sceAudiocodecGetEDRAM(u32 ctxPtr, int codec) {
+1 -1
View File
@@ -50,7 +50,7 @@ struct SceAudiocodecCodec {
u32 inBuf; // 0x18 the raw frame to decode
s32 srcBytesRead; // 0x1c written by the decoder
u32 outBuf; // 0x20 where decoded PCM goes
s32 dstSamplesWritten; // 0x24 written by the decoder
s32 dstBytesWritten; // 0x24 written by the decoder, in bytes like srcBytesRead
// Codec-specific, 0x28..0x67.
union {
+1 -1
View File
@@ -999,7 +999,7 @@ static u32 npdrmRead(FileNode *f, u8 *data, int size) {
PGD_DESC *pgd = f->pgdInfo;
if (!pgd) {
// When pgdInfo is null, fall back to reading the file in non-encrypted mode
WARN_LOG(Log::IO, "npdrmRead: pgdInfo is null for file %s, reading as non-encrypted", f->fullpath.c_str());
DEBUG_LOG(Log::IO, "npdrmRead: pgdInfo is null for file %s, reading as non-encrypted", f->fullpath.c_str());
return (u32)pspFileSystem.ReadFile(f->handle, data, size);
}
u32 block, offset, blockPos;
+18 -1
View File
@@ -480,7 +480,24 @@ static int sceMp3Init(u32 mp3) {
// TODO: Should return 0x80671301 (unsupported version?)
WARN_LOG_REPORT(Log::ME, "sceMp3Init: invalid data: not MPEG v1");
}
if (samplerate != 44100 && sdkver < 3090500) {
// DELIBERATELY MORE LENIENT THAN A PSP.
//
// libmp3.prx only accepts a rate other than 44.1kHz from a game built with SDK 3.09.05 or
// later - it compares the compiled SDK version against 0x030904FF - and audio/mp3/init carries
// the hardware's answers for the rest: 48kHz and 32kHz both come back as 0x80671302.
//
// We only give that answer to something that declares no SDK version at all, which in practice
// means the test. Anything that declares one gets its rate accepted whatever it is. That is
// what PPSSPP has always done here, by accident - the threshold was written as decimal 3090500
// rather than 0x03090500, so every real version cleared it - but it is worth keeping on
// purpose. Beats and games like it build levels out of MP3s the user supplies, and refusing an
// ordinary 48kHz file looks like a bug to whoever supplied it.
//
// Note this only applies here. Under DisableHLE for sceMp3 the check is inside libmp3.prx and
// it does refuse the file, because the only thing we hand it is the sample rate index - which
// it also uses to look up the rate it plays at, so claiming 44.1kHz to get past the check would
// play the stream at the wrong speed. To be strict again, compare against 0x030904FF.
if (samplerate != 44100 && sdkver == 0) {
return hleDelayResult(hleLogError(Log::ME, SCE_MP3_ERROR_BAD_SAMPLE_RATE, "invalid data: not 44.1kHz"), "mp3 init", PARSE_DELAY_MS);
}
+1 -1
View File
@@ -174,7 +174,7 @@ static const u8 *MpegBaseFramePointer(u32 addr, int size) {
//
// Untangling it into plain planes costs one pass per frame, which keeps the conversion below
// readable and is not where the time goes.
static bool ReadTiledYCbCr(const u32 *buffers, int width, int height,
bool ReadTiledYCbCr(const u32 *buffers, int width, int height,
std::vector<u8> &luma, std::vector<u8> &cb, std::vector<u8> &cr) {
const int width2 = width >> 1;
const int height2 = height >> 1;
+6
View File
@@ -35,3 +35,9 @@ void __MpegBaseDoState(PointerWrap &p);
// instead. The payload is moved out and dropped from the table, so each one is decoded once.
// Empty if nothing was copied to that address.
std::vector<u8> MpegBaseTakePESPacket(u32 dest);
// Un-tiles a decoded frame from the eight buffers the Media Engine lays it out in into three
// planes. The buffers are in sceVideocodec's order: four luma, then four chroma. cb and cr come
// out at half width and half height, as YUV420 does.
bool ReadTiledYCbCr(const u32 *buffers, int width, int height,
std::vector<u8> &luma, std::vector<u8> &cb, std::vector<u8> &cr);
+47 -4
View File
@@ -653,11 +653,54 @@ static int sceVideocodecSetMode(u32 ctxAddr, int type) {
return hleLogWarning(Log::ME, 0, "UNIMPL");
}
// 0xD95C24D5. Copies a decoded YCbCr frame between two sets of buffers through the ME (op
// 0x21521BE5) - the videocodec-level counterpart of sceMpegBaseYCrCbCopy. mpeg.prx calls it on the
// sceMpegAvcCopyYCbCr path. Nothing we run reaches it, so it stays a stub for now.
// 0xD95C24D5. Hands a decoded frame back to the caller as three planes, which is what
// sceMpegAvcCopyYCbCr is built on - the videocodec-level counterpart of sceMpegBaseYCrCbCopy.
// Games that want the raw YCbCr rather than letting sceMpegbase convert to RGB use this and nothing
// else (Monster Hunter Portable 3rd calls it once per decoded frame and never calls a Csc).
//
// mpeg.prx builds the descriptor on its own stack (AvcCopyDeeper in mpeg.prx 2.60) and avcodec.prx
// reads it back at 0x800015c4, which is where the layout below comes from:
//
// 0x00 width in pixels 0x04 height in pixels
// 0x0c the eight frame buffers, but ordered 0,2,4,6 then 1,3,5,7 rather than 0..7
// 0x2c destination Y, then Cb at +width*height and Cr a further width*height/4 on - so the
// three planes are contiguous, and the caller gets ordinary planar YUV420.
static int sceVideocodecCopyYCbCr(u32 ctxAddr, int type) {
return hleLogWarning(Log::ME, 0, "UNIMPL");
if (!Memory::IsValidRange(ctxAddr, 0x38)) {
return hleLogError(Log::ME, -1, "bad descriptor pointer");
}
const int width = (int)Memory::ReadUnchecked_U32(ctxAddr + 0x00);
const int height = (int)Memory::ReadUnchecked_U32(ctxAddr + 0x04);
if (width <= 0 || height <= 0 || width > 1024 || height > 1024) {
return hleLogError(Log::ME, -1, "unreasonable frame size %dx%d", width, height);
}
// Back into the order the rest of our code uses: four luma, then four chroma.
static const int fromDescriptor[8] = { 0, 2, 4, 6, 1, 3, 5, 7 };
u32 buffers[8]{};
for (int i = 0; i < 8; i++) {
buffers[fromDescriptor[i]] = Memory::ReadUnchecked_U32(ctxAddr + 0x0c + i * 4);
}
std::vector<u8> luma, cb, cr;
if (!ReadTiledYCbCr(buffers, width, height, luma, cb, cr)) {
return hleLogError(Log::ME, -1, "YCbCr buffers not readable");
}
const u32 dst[3] = {
Memory::ReadUnchecked_U32(ctxAddr + 0x2c),
Memory::ReadUnchecked_U32(ctxAddr + 0x30),
Memory::ReadUnchecked_U32(ctxAddr + 0x34),
};
const std::vector<u8> *planes[3] = { &luma, &cb, &cr };
for (int i = 0; i < 3; i++) {
const u32 size = (u32)planes[i]->size();
if (!Memory::IsValidRange(dst[i], size)) {
return hleLogError(Log::ME, -1, "plane %d (%08x, %d bytes) not writable", i, dst[i], size);
}
Memory::MemcpyUnchecked(dst[i], planes[i]->data(), size);
}
return hleLogDebug(Log::ME, 0, "%dx%d -> %08x %08x %08x", width, height, dst[0], dst[1], dst[2]);
}
const HLEFunction sceVideocodec[] = {