sceAtrac: Charge setting data and decoding what the ME takes

Setting data decodes and throws away the frames before the first sample,
so on hardware it costs a decoder setup plus that decode, with the caller
waiting: ~900us for mono Atrac3 and ~3.5ms for stereo Atrac3+, whatever the
buffer size (pspautotests threads/scheduling/callcosts). It was charged
100us.

Decoding a frame now costs what the same frame costs through
sceAudiocodecDecode, through the shared ME queue, instead of a flat
2300us. That's about the same for stereo Atrac3+ and less for Atrac3
(685us mono, ~1100us stereo). The first Atrac3+ frames after setup still
come out ~500us short.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-29 10:17:46 -06:00
1 parent c1e70c9686
commit 4ba981b5b3
5 files changed
+54 -18

No files matched your search

+3
View File
@@ -222,6 +222,8 @@ public:
virtual int ResetPlayPosition(int sample, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf, bool *delay) = 0;
virtual int GetBufferInfoForResetting(AtracResetBufferInfo *bufferInfo, int sample, bool *delay) = 0; // NOTE: Not const! This can cause SkipFrames!
virtual int SetData(const Track &track, u32 buffer, u32 readSize, u32 bufferSize, u32 fileSize, int outputChannels, bool isAA3) = 0;
// How many frames the last SetData decoded and threw away, to get to the first sample.
int SkippedFramesOnSetData() const { return setDataSkippedFrames_; }
virtual int GetSecondBufferInfo(u32 *fileOffset, u32 *desiredSize) const = 0;
virtual int SetSecondBuffer(u32 secondBuffer, u32 secondBufferSize) = 0;
@@ -242,6 +244,7 @@ public:
protected:
u16 outputChannels_ = 2;
int setDataSkippedFrames_ = 0;
// TODO: Save the internal state of this, now technically possible.
AudioDecoder *decoder_ = nullptr;
+2 -1
View File
@@ -1019,8 +1019,9 @@ int Atrac2::SetData(const Track &track, u32 bufferAddr, u32 readSize, u32 buffer
info.fileDataEnd, info.decodePos, info.numSkipFrames, info.numChan
);
int skipCount = 0; // TODO: use for delay
int skipCount = 0;
retval = SkipFrames(&skipCount);
setDataSkippedFrames_ = skipCount;
// Seen in Mui Mui house. Things go very wrong after this..
if (retval == SCE_ERROR_ATRAC_API_FAIL) {
+33 -15
View File
@@ -32,8 +32,11 @@
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceUtility.h"
#include "Core/HLE/sceVideocodec.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/HLE/scePower.h"
#include "Core/HLE/sceAtrac.h"
#include "Core/HLE/sceAudiocodec.h"
#include "Core/HLE/AtracCtx.h"
#include "Core/HLE/AtracCtx2.h"
#include "Core/System.h"
@@ -81,7 +84,21 @@
// TODO: We should add checks that the utility module is loaded.
static const int atracDecodeDelay = 2300;
// The Media Engine does the decoding while the caller waits. Setting data decodes the frames before
// the first sample (thrown away), which on hardware makes it cost a decoder setup plus a frame
// decode: ~900us for mono Atrac3, ~3.5ms for stereo Atrac3+ (pspautotests threads/scheduling/callcosts).
static int AtracFrameUs(const AtracBase *atrac) {
return AudioCodecDecodeUs(atrac->CodecType(), atrac->Channels(), atrac->BytesPerFrame());
}
static int AtracSetDataDelay(const AtracBase *atrac) {
const int us = AudioCodecInitUs(atrac->CodecType(), atrac->Channels() == 1) + atrac->SkippedFramesOnSetData() * AtracFrameUs(atrac);
return MEScheduleJob(PowerScaleFromDefaultClock(us));
}
static int AtracDecodeDelay(const AtracBase *atrac) {
return MEScheduleJob(PowerScaleFromDefaultClock(AtracFrameUs(atrac)));
}
static bool atracInited = true;
static AtracBase *atracContexts[PSP_MAX_ATRAC_IDS];
@@ -94,7 +111,7 @@ static int g_atracBSS = 0;
static bool g_muteFlag[PSP_MAX_ATRAC_IDS]{}; // Not saved, just for debugging.
// On a PSP, the Media Engine does the decoding, and the samples land in the output buffer when
// sceAtracDecodeData returns, about atracDecodeDelay later. A game can still be playing out of that
// sceAtracDecodeData returns, a frame decode later. A game can still be playing out of that
// memory in the meantime: Fired Up decodes into a buffer that overlaps the first 16 samples of the
// one it has just handed to sceAudio, and relies on the mixer having read them first. So the
// samples are written just before the thread wakes rather than when the call is made.
@@ -423,6 +440,7 @@ static u32 sceAtracDecodeData(int atracID, u32 outAddr, u32 numSamplesAddr, u32
}
if (ret == 0 || ret == SCE_ERROR_ATRAC_API_FAIL) {
const int delay = AtracDecodeDelay(atrac);
const u32 written = std::min((u32)(atrac->GetOutputChannels() * 2 * numSamplesWritten), (u32)previous.size());
if (outPtr && written != 0) {
AtracPendingOutput pending{ ++g_pendingOutputId, outAddr };
@@ -430,10 +448,10 @@ static u32 sceAtracDecodeData(int atracID, u32 outAddr, u32 numSamplesAddr, u32
memcpy(outPtr, previous.data(), previous.size());
g_pendingOutput.push_back(std::move(pending));
// Just ahead of the thread waking up.
CoreTiming::ScheduleEvent(usToCycles(atracDecodeDelay) - 1, g_atracOutputEvent, g_pendingOutputId);
CoreTiming::ScheduleEvent(usToCycles(delay) - 1, g_atracOutputEvent, g_pendingOutputId);
}
// Decoded or at least attempted to decode data, delay thread
return hleDelayResult(hleNoLog(ret), "atrac decode data", atracDecodeDelay);
return hleDelayResult(hleNoLog(ret), "atrac decode data", delay);
}
return hleNoLog(ret);
@@ -753,7 +771,7 @@ static u32 sceAtracSetHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u32 b
}
// not sure the real delay time
return hleDelayResult(hleLogDebug(Log::Atrac, ret), "atrac set data", 100);
return hleDelayResult(hleLogDebug(Log::Atrac, ret), "atrac set data", AtracSetDataDelay(atrac));
}
static u32 sceAtracSetSecondBuffer(int atracID, u32 secondBuffer, u32 secondBufferSize) {
@@ -788,7 +806,7 @@ static u32 sceAtracSetData(int atracID, u32 buffer, u32 bufferSize) {
return hleLogError(Log::Atrac, ret);
}
return hleDelayResult(hleLogDebug(Log::Atrac, ret), "atrac set data", 100);
return hleDelayResult(hleLogDebug(Log::Atrac, ret), "atrac set data", AtracSetDataDelay(atrac));
}
static int sceAtracSetDataAndGetID(u32 buffer, int bufferSize) {
@@ -819,7 +837,7 @@ static int sceAtracSetDataAndGetID(u32 buffer, int bufferSize) {
return hleLogError(Log::Atrac, ret);
}
return hleDelayResult(hleLogDebug(Log::Atrac, atracID), "atrac set data", 100);
return hleDelayResult(hleLogDebug(Log::Atrac, atracID), "atrac set data", AtracSetDataDelay(atracContexts[atracID]));
}
static int sceAtracSetHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bufferSize) {
@@ -845,7 +863,7 @@ static int sceAtracSetHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 buffer
return hleLogError(Log::Atrac, ret);
}
return hleDelayResult(hleLogDebug(Log::Atrac, atracID), "atrac set data", 100);
return hleDelayResult(hleLogDebug(Log::Atrac, atracID), "atrac set data", AtracSetDataDelay(atracContexts[atracID]));
}
static u32 sceAtracStartEntry() {
@@ -959,7 +977,7 @@ static int sceAtracSetMOutHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u
// Must not delay.
return hleLogError(Log::Atrac, ret);
}
return hleDelayResult(hleLogDebugOrError(Log::Atrac, ret), "atrac set data mono", 100);
return hleDelayResult(hleLogDebugOrError(Log::Atrac, ret), "atrac set data mono", AtracSetDataDelay(atrac));
}
// Note: This doesn't seem to be part of any available libatrac3plus library.
@@ -985,7 +1003,7 @@ static u32 sceAtracSetMOutData(int atracID, u32 buffer, u32 bufferSize) {
return hleLogError(Log::Atrac, ret);
}
// It's OK if this fails, at least with NO_MONO...
return hleDelayResult(hleLogDebugOrError(Log::Atrac, ret), "atrac set data mono", 100);
return hleDelayResult(hleLogDebugOrError(Log::Atrac, ret), "atrac set data mono", AtracSetDataDelay(atrac));
}
// Note: This doesn't seem to be part of any available libatrac3plus library.
@@ -1012,7 +1030,7 @@ static int sceAtracSetMOutDataAndGetID(u32 buffer, u32 bufferSize) {
UnregisterAndDeleteAtrac(atracID);
return hleLogError(Log::Atrac, ret);
}
return hleDelayResult(hleLogDebugOrError(Log::Atrac, atracID), "atrac set data", 100);
return hleDelayResult(hleLogDebugOrError(Log::Atrac, atracID), "atrac set data", AtracSetDataDelay(atracContexts[atracID]));
}
static int sceAtracSetMOutHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bufferSize) {
@@ -1041,7 +1059,7 @@ static int sceAtracSetMOutHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bu
UnregisterAndDeleteAtrac(atracID);
return hleLogError(Log::Atrac, ret);
}
return hleDelayResult(hleLogDebug(Log::Atrac, atracID), "atrac set data", 100);
return hleDelayResult(hleLogDebug(Log::Atrac, atracID), "atrac set data", AtracSetDataDelay(atracContexts[atracID]));
}
static int sceAtracSetAA3DataAndGetID(u32 buffer, u32 bufferSize, u32 fileSize, u32 metadataSizeAddr) {
@@ -1063,7 +1081,7 @@ static int sceAtracSetAA3DataAndGetID(u32 buffer, u32 bufferSize, u32 fileSize,
return hleLogError(Log::Atrac, ret);
}
return hleDelayResult(hleLogDebug(Log::Atrac, atracID), "atrac set aa3 data", 100);
return hleDelayResult(hleLogDebug(Log::Atrac, atracID), "atrac set aa3 data", AtracSetDataDelay(atracContexts[atracID]));
}
static int sceAtracSetAA3HalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bufferSize, u32 fileSize) {
@@ -1089,7 +1107,7 @@ static int sceAtracSetAA3HalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 buf
return hleLogError(Log::Atrac, ret);
}
return hleDelayResult(hleLogDebug(Log::Atrac, atracID), "atrac set data", 100);
return hleDelayResult(hleLogDebug(Log::Atrac, atracID), "atrac set data", AtracSetDataDelay(atracContexts[atracID]));
}
// TODO: Should see if these are stored contiguously in memory somewhere, or if there really are
@@ -1155,7 +1173,7 @@ static int sceAtracLowLevelDecode(int atracID, u32 sourceAddr, u32 sourceBytesCo
}
NotifyMemInfo(MemBlockFlags::WRITE, samplesAddr, bytesWritten, "AtracLowLevelDecode");
return hleDelayResult(hleLogDebug(Log::Atrac, retval), "low level atrac decode data", atracDecodeDelay);
return hleDelayResult(hleLogDebug(Log::Atrac, retval), "low level atrac decode data", AtracDecodeDelay(atrac));
}
// These three are the external interface used by sceSas' AT3 integration.
+11 -2
View File
@@ -239,15 +239,19 @@ static int MECall(int result, int us) {
return hleDelayResult(result, "audiocodec", MEScheduleJob(PowerScaleFromDefaultClock(us)));
}
static int InitUs(int codec, const SceAudiocodecCodec *ctx) {
int AudioCodecInitUs(int codec, bool monoAt3Plus) {
switch (codec) {
case PSP_CODEC_AT3PLUS: return ((ctx->fmt.at3.formatByte1 >> 2) & 7) == 1 ? 524 : 646;
case PSP_CODEC_AT3PLUS: return monoAt3Plus ? 524 : 646;
case PSP_CODEC_AT3: return 210;
case PSP_CODEC_MP3: return 517;
default: return 230;
}
}
static int InitUs(int codec, const SceAudiocodecCodec *ctx) {
return AudioCodecInitUs(codec, codec == PSP_CODEC_AT3PLUS && ((ctx->fmt.at3.formatByte1 >> 2) & 7) == 1);
}
// libmp4.prx puts the sample rate here. On hardware 22050 and 44100 are accepted, 0 and 12345
// aren't; that the rest of the standard AAC rates are accepted is an assumption. 0 if not valid.
static int AacSampleRateFromContext(const SceAudiocodecCodec *ctx) {
@@ -395,6 +399,11 @@ static int EstimateDecodeUs(int codec, int channels, int frameBytes, const SceAu
}
}
int AudioCodecDecodeUs(int codec, int channels, int frameBytes) {
_dbg_assert_(codec == PSP_CODEC_AT3PLUS || codec == PSP_CODEC_AT3);
return EstimateDecodeUs(codec, channels, frameBytes, nullptr);
}
static int sceAudiocodecInit(u32 ctxPtr, int codec) {
return __AudioCodecInitCommon(ctxPtr, codec, false);
}
+5
View File
@@ -128,3 +128,8 @@ class AudioDecoder;
extern std::map<u32, AudioDecoder *> g_audioDecoderContexts;
bool IsAtrac3StreamJointStereo(int codecType, int bytesPerFrame, int channels);
// ME time at the default clock, for sceAtrac too (it drives the same decoder): setting up a decoder,
// and decoding one frame. AudioCodecDecodeUs takes Atrac3 and Atrac3+ only.
int AudioCodecInitUs(int codec, bool monoAt3Plus);
int AudioCodecDecodeUs(int codec, int channels, int frameBytes);