mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
ME: Charge measured times for sceVideocodec calls and the rest of sceAudiocodec
Measured on a PSP (pspautotests video/mp4/mp4timing, audio/audiocodec/timing): - sceVideocodec Open, GetEDRAM, GetVersion and ReleaseEDRAM take ~70-150us, Init ~26.6ms (sceMpegCreate is 27-28ms), Delete ~21ms (was 2ms), and Stop 132us with nothing held back. All go through the ME queue now. - Decodes that return no picture take as long as those that do; they were free. - Open reports the EDRAM the decoder needs (0x3c2c) at ctx+0x18, which mpeg.prx passes on to GetEDRAM. - sceAudiocodec: failed decodes (214/142/169us) and mono Atrac3+ init (524us). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
c162eb3d74
commit
c2bc2d9308
2 files changed
+58
-30
No files matched your search
+13
-11
@@ -232,15 +232,16 @@ void __AudioCodecShutdown() {
|
||||
// Atrac3 80 56 210 56 3
|
||||
// MP3 79 57 517 57 82
|
||||
// AAC 57 57 230 55 3
|
||||
// A GetInfo of 3us didn't reach the ME. Calls that fail in the ME take time too; how long a failed
|
||||
// decode takes isn't measured.
|
||||
// A GetInfo of 3us didn't reach the ME. Initializing a mono Atrac3+ decoder (as InitMono does for
|
||||
// libatrac3plus.prx's MOut functions) takes 524us. Failed decodes take 214us for an Atrac3+
|
||||
// bitstream error, 142us for a bad Atrac3+ frame header, and 169us for an Atrac3 bitstream error.
|
||||
static int MECall(int result, int us) {
|
||||
return hleDelayResult(result, "audiocodec", MEScheduleJob(PowerScaleFromDefaultClock(us)));
|
||||
}
|
||||
|
||||
static int InitUs(int codec) {
|
||||
static int InitUs(int codec, const SceAudiocodecCodec *ctx) {
|
||||
switch (codec) {
|
||||
case PSP_CODEC_AT3PLUS: return 646;
|
||||
case PSP_CODEC_AT3PLUS: return ((ctx->fmt.at3.formatByte1 >> 2) & 7) == 1 ? 524 : 646;
|
||||
case PSP_CODEC_AT3: return 210;
|
||||
case PSP_CODEC_MP3: return 517;
|
||||
default: return 230;
|
||||
@@ -336,7 +337,7 @@ static int __AudioCodecInitCommon(u32 ctxPtr, int codec, bool mono) {
|
||||
break;
|
||||
case PSP_CODEC_AAC:
|
||||
if (!AacSampleRateFromContext(ctx)) {
|
||||
return MECall(hleLogError(Log::ME, SCE_AVCODEC_ERROR_UNSUPPORTED, "bad AAC sample rate %d", ctx->fmt.aac.sampleRate), InitUs(codec));
|
||||
return MECall(hleLogError(Log::ME, SCE_AVCODEC_ERROR_UNSUPPORTED, "bad AAC sample rate %d", ctx->fmt.aac.sampleRate), InitUs(codec, ctx));
|
||||
}
|
||||
primingFrames = 2;
|
||||
break;
|
||||
@@ -346,7 +347,7 @@ static int __AudioCodecInitCommon(u32 ctxPtr, int codec, bool mono) {
|
||||
CalculateInputBytesAndChannelsAt3Plus(ctx, &bytesPerFrame, &channels);
|
||||
if (channels == 0) {
|
||||
ctx->err = 0x202;
|
||||
return MECall(hleLogError(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "bad Atrac3+ format byte %02x", ctx->fmt.at3.formatByte1), InitUs(codec));
|
||||
return MECall(hleLogError(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "bad Atrac3+ format byte %02x", ctx->fmt.at3.formatByte1), InitUs(codec, ctx));
|
||||
}
|
||||
primingFrames = 1;
|
||||
break;
|
||||
@@ -357,7 +358,7 @@ static int __AudioCodecInitCommon(u32 ctxPtr, int codec, bool mono) {
|
||||
bool jointStereo;
|
||||
if (!Atrac3LayoutFromContext(ctx, &bytesPerFrame, &channels, &jointStereo)) {
|
||||
ctx->err = 0x186;
|
||||
return MECall(hleLogError(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "bad Atrac3 parameter %08x", *(const u32_le *)ctx->fmt.raw), InitUs(codec));
|
||||
return MECall(hleLogError(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "bad Atrac3 parameter %08x", *(const u32_le *)ctx->fmt.raw), InitUs(codec, ctx));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -368,7 +369,7 @@ static int __AudioCodecInitCommon(u32 ctxPtr, int codec, bool mono) {
|
||||
CreateDecoderForContext(ctxPtr, audioType);
|
||||
// Not in CreateDecoderForContext: a state load restores what's left of it instead.
|
||||
g_primingFrames[ctxPtr] = primingFrames;
|
||||
return MECall(hleLogDebug(Log::ME, 0), InitUs(codec));
|
||||
return MECall(hleLogDebug(Log::ME, 0), InitUs(codec, ctx));
|
||||
}
|
||||
|
||||
// How long the ME takes over one frame, in microseconds at the default 222MHz clock. Fitted to
|
||||
@@ -377,7 +378,8 @@ static int __AudioCodecInitCommon(u32 ctxPtr, int codec, bool mono) {
|
||||
// Atrac3 stereo: 1138 at 0x180, 1063 at 0xC0 joint stereo. Mono: 685 at 0x98.
|
||||
// MP3 MPEG1 (1152 samples): 2575 at 418 bytes, 2698 at 1045. MPEG2 (576): 1411 at 104, 1464 at 209.
|
||||
// AAC-LC stereo 44.1kHz: 1651 at ~190 bytes, 1990 at ~373, 2042 at ~559.
|
||||
// The mono Atrac3+ slope is a guess from its one data point.
|
||||
// The mono Atrac3+ slope is a guess from its one data point. Content matters as well as size: the
|
||||
// synthetic two-tone AAC in video/mp4 decodes about 15% faster than music at the same bitrate.
|
||||
static int EstimateDecodeUs(int codec, int channels, int frameBytes, const SceAudiocodecCodec *ctx) {
|
||||
switch (codec) {
|
||||
case PSP_CODEC_AT3PLUS:
|
||||
@@ -429,7 +431,7 @@ static int sceAudiocodecDecode(u32 ctxPtr, int codec) {
|
||||
ctx->err = frameError;
|
||||
ctx->srcBytesRead = 0;
|
||||
ctx->dstBytesWritten = 0;
|
||||
return MECall(hleLogWarning(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "Atrac3+ frame doesn't match the context: err %03x", frameError), 100);
|
||||
return MECall(hleLogWarning(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "Atrac3+ frame doesn't match the context: err %03x", frameError), 142);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -517,7 +519,7 @@ static int sceAudiocodecDecode(u32 ctxPtr, int codec) {
|
||||
ctx->err = codec == PSP_CODEC_AT3PLUS ? 0x20a : 0x182;
|
||||
ctx->srcBytesRead = 0;
|
||||
ctx->dstBytesWritten = 0;
|
||||
return MECall(hleLogWarning(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "%s frame failed to decode", GetCodecName(codec)), 100);
|
||||
return MECall(hleLogWarning(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "%s frame failed to decode", GetCodecName(codec)), codec == PSP_CODEC_AT3PLUS ? 214 : 169);
|
||||
}
|
||||
if (!result) {
|
||||
ctx->err = 0x20b;
|
||||
|
||||
+45
-19
@@ -436,6 +436,24 @@ static void WriteTiledYCbCr(const u32 *buffers, const AvcDecoder &dec, int width
|
||||
}
|
||||
}
|
||||
|
||||
// Every call here that reaches the ME blocks while it answers. Measured at 222MHz in pspautotests
|
||||
// video/mp4/mp4timing: Open, GetEDRAM, GetVersion and ReleaseEDRAM called directly, the rest through
|
||||
// mpeg.prx's thin wrappers. sceMpegCreate (Open, Init, GetVersion, SetMemory) took 26.9-28.0ms,
|
||||
// nearly all of it Init and SetMemory, which are charged together to Init here. sceMpegDelete took
|
||||
// 21.2ms, and 35.9ms in a run that had stopped the decoder twice first. An sceMpegAvcDecodeStop with
|
||||
// nothing held back took 132us (with three pictures to hand over, 4.9ms, the rest being their output).
|
||||
static const int openUs = 96;
|
||||
static const int getEdramUs = 146;
|
||||
static const int getVersionUs = 94;
|
||||
static const int releaseEdramUs = 66;
|
||||
static const int initUs = 26600;
|
||||
static const int stopUs = 132;
|
||||
static const int deleteUs = 21000;
|
||||
|
||||
static int MECall(int result, const char *reason, int us) {
|
||||
return hleDelayResult(result, reason, MEScheduleJob(PowerScaleFromDefaultClock(us)));
|
||||
}
|
||||
|
||||
static int sceVideocodecOpen(u32 ctxAddr, int type) {
|
||||
if (!Memory::IsValidRange(ctxAddr, 96)) {
|
||||
return hleLogError(Log::ME, -1, "bad context pointer");
|
||||
@@ -445,7 +463,11 @@ static int sceVideocodecOpen(u32 ctxAddr, int type) {
|
||||
return hleLogError(Log::ME, -1, "built without ffmpeg, can't decode video");
|
||||
}
|
||||
g_videocodecCtxs[ctxAddr].type = type;
|
||||
return hleLogInfo(Log::ME, 0, "type %d", type);
|
||||
if (type == 0) {
|
||||
// The EDRAM the decoder needs, which mpeg.prx reads back and passes to GetEDRAM.
|
||||
Memory::WriteUnchecked_U32(0x3c2c, ctxAddr + CTX_EDRAM_SIZE);
|
||||
}
|
||||
return MECall(hleLogInfo(Log::ME, 0, "type %d", type), "videocodec open", openUs);
|
||||
}
|
||||
|
||||
static int sceVideocodecInit(u32 ctxAddr, int type) {
|
||||
@@ -458,7 +480,7 @@ static int sceVideocodecInit(u32 ctxAddr, int type) {
|
||||
vctx.decoder = new AvcDecoder();
|
||||
vctx.frameCount = 0;
|
||||
vctx.type = type;
|
||||
return hleLogInfo(Log::ME, 0, "type %d", type);
|
||||
return MECall(hleLogInfo(Log::ME, 0, "type %d", type), "videocodec init", initUs);
|
||||
}
|
||||
|
||||
// See g_meRam for why this doesn't come out of the game's memory.
|
||||
@@ -483,7 +505,7 @@ static int sceVideocodecGetEDRAM(u32 ctxAddr, int type) {
|
||||
// works in 64-byte grains, so the two only differ in what they mean, not in value.
|
||||
Memory::WriteUnchecked_U32(addr, ctxAddr + CTX_EDRAM);
|
||||
Memory::WriteUnchecked_U32(addr, ctxAddr + CTX_EDRAM_RAW);
|
||||
return hleLogInfo(Log::ME, 0, "%u bytes at %08x in ME memory", size, addr);
|
||||
return MECall(hleLogInfo(Log::ME, 0, "%u bytes at %08x in ME memory", size, addr), "videocodec getedram", getEdramUs);
|
||||
}
|
||||
|
||||
static int sceVideocodecReleaseEDRAM(u32 ctxAddr) {
|
||||
@@ -502,7 +524,7 @@ static int sceVideocodecReleaseEDRAM(u32 ctxAddr) {
|
||||
}
|
||||
Memory::WriteUnchecked_U32(0, ctxAddr + CTX_EDRAM);
|
||||
Memory::WriteUnchecked_U32(0, ctxAddr + CTX_EDRAM_RAW);
|
||||
return hleLogInfo(Log::ME, 0, "released %08x", token);
|
||||
return MECall(hleLogInfo(Log::ME, 0, "released %08x", token), "videocodec releaseedram", releaseEdramUs);
|
||||
}
|
||||
|
||||
static int sceVideocodecDecode(u32 ctxAddr, int type) {
|
||||
@@ -616,34 +638,38 @@ static int sceVideocodecDecode(u32 ctxAddr, int type) {
|
||||
// sceMpegAvcDecode (5.8ms) less sceMpegAvcCsc alone (2.4ms), in pspautotests
|
||||
// video/mpeg/playertiming. Movie players that present every decoded frame after a single
|
||||
// vblank wait rely on decode, colour conversion and blit adding up to more than a vblank.
|
||||
// It takes as long when the decoder holds the picture back: the first sceMpegAvcDecode calls of
|
||||
// a stream, which return none, took 4.2-5.4ms in video/mp4/mp4timing. Until a picture has told
|
||||
// us the size, assume full screen.
|
||||
int delayUs = 0;
|
||||
if (gotFrame && width > 0 && height > 0) {
|
||||
delayUs = MEScheduleJob(PowerScaleFromDefaultClock((int)(3400LL * width * height / (480 * 272))));
|
||||
if (auBytes > 0) {
|
||||
const int w = vctx.decoder->Width() > 0 ? vctx.decoder->Width() : 480;
|
||||
const int h = vctx.decoder->Height() > 0 ? vctx.decoder->Height() : 272;
|
||||
delayUs = MEScheduleJob(PowerScaleFromDefaultClock((int)(3400LL * w * h / (480 * 272))));
|
||||
}
|
||||
|
||||
if (delayUs > 0) {
|
||||
return hleDelayResult(hleLogDebug(Log::ME, 0, "type %d, %d bytes -> frame %dx%d",
|
||||
type, auBytes, width, height), "videocodec decode", delayUs);
|
||||
return hleDelayResult(hleLogDebug(Log::ME, 0, "type %d, %d bytes -> %s %dx%d",
|
||||
type, auBytes, gotFrame ? "frame" : "no frame yet", width, height), "videocodec decode", delayUs);
|
||||
}
|
||||
return hleLogDebug(Log::ME, 0, "type %d, %d bytes -> %s %dx%d",
|
||||
type, auBytes, gotFrame ? "frame" : "no frame yet", width, height);
|
||||
}
|
||||
|
||||
// Stopping or deleting the decoder is an ME round-trip and takes real time on hardware. Returning
|
||||
// immediately is not correct, because a game can be relying on a thread of its own getting to run
|
||||
// once more before it tears things down. Jak and Daxter deletes its video_sound_thread straight
|
||||
// after sceVideocodecDelete without waiting for it to exit, and with no time passing here the audio
|
||||
// thread never gets to deliver the wake that would let it exit - so the delete fails with
|
||||
// NOT_DORMANT and the thread lives on, reading a context the game has already freed.
|
||||
// One audio mix block is 64 samples at 44100Hz, about 1.45ms, so stay above that.
|
||||
static const int videocodecTeardownDelayUs = 2000;
|
||||
// Returning immediately from Stop or Delete is not correct, because a game can be relying on a
|
||||
// thread of its own getting to run once more before it tears things down. Jak and Daxter deletes
|
||||
// its video_sound_thread straight after sceVideocodecDelete without waiting for it to exit, and
|
||||
// with no time passing here the audio thread never gets to deliver the wake that would let it exit
|
||||
// - so the delete fails with NOT_DORMANT and the thread lives on, reading a context the game has
|
||||
// already freed. One audio mix block is 64 samples at 44100Hz, about 1.45ms, which Delete's
|
||||
// measured time is well above.
|
||||
|
||||
static int sceVideocodecStop(u32 ctxAddr, int type) {
|
||||
auto it = g_videocodecCtxs.find(ctxAddr);
|
||||
if (it != g_videocodecCtxs.end() && it->second.decoder) {
|
||||
it->second.decoder->Flush();
|
||||
}
|
||||
return hleDelayResult(hleLogInfo(Log::ME, 0), "videocodec stop", videocodecTeardownDelayUs);
|
||||
return MECall(hleLogInfo(Log::ME, 0), "videocodec stop", stopUs);
|
||||
}
|
||||
|
||||
static int sceVideocodecDelete(u32 ctxAddr, int type) {
|
||||
@@ -652,7 +678,7 @@ static int sceVideocodecDelete(u32 ctxAddr, int type) {
|
||||
FreeContext(it->second);
|
||||
g_videocodecCtxs.erase(it);
|
||||
}
|
||||
return hleDelayResult(hleLogInfo(Log::ME, 0), "videocodec delete", videocodecTeardownDelayUs);
|
||||
return MECall(hleLogInfo(Log::ME, 0), "videocodec delete", deleteUs);
|
||||
}
|
||||
|
||||
static int sceVideocodecGetVersion(u32 ctxAddr, int type) {
|
||||
@@ -661,7 +687,7 @@ static int sceVideocodecGetVersion(u32 ctxAddr, int type) {
|
||||
}
|
||||
// The value a real PSP returns, read with JpcspTrace.
|
||||
Memory::WriteUnchecked_U32(0x78, ctxAddr + CTX_VERSION);
|
||||
return hleLogInfo(Log::ME, 0);
|
||||
return MECall(hleLogInfo(Log::ME, 0), "videocodec getversion", getVersionUs);
|
||||
}
|
||||
|
||||
static int sceVideocodecGetSEI(u32 ctxAddr, int type) {
|
||||
|
||||
Reference in new issue
Block a user