Merge pull request #22250 from hrydgard/audiocodec-field-names

sceAudiocodec: Use more information for decoding, describe more fields
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-09-07 13:56:11 -06:00
commit fdd9baaba1
3 files changed
+212 -81

No files matched your search

+7 -7
View File
@@ -236,9 +236,9 @@ int InitContextFromTrackInfo(SceAtracContext *ctx, const TrackInfo *wave, u32 bu
if ((ctx->info).codec != PSP_CODEC_AT3) {
// At3plus
// Configure the codec for the sample size, or whatever that data is.
(ctx->codec).unk40 = wave->sampleSizeMaybe;
(ctx->codec).unk48 = 0;
(ctx->codec).unk41 = wave->tailFlag;
(ctx->codec).fmt.at3.formatByte1 = wave->sampleSizeMaybe;
(ctx->codec).fmt.at3.at3Related = 0;
(ctx->codec).fmt.at3.formatByte2 = wave->tailFlag;
return 0;
}
// At3. Set up the hardware codec (hopefully we can correctly support this in sceAudiocodec and thus sceAtrac LLE in the future)
@@ -246,10 +246,10 @@ int InitContextFromTrackInfo(SceAtracContext *ctx, const TrackInfo *wave, u32 bu
for (int counter = 4; counter >= 0; counter--) {
if ((g_at3BitrateMeta[counter].sampleSize == (ctx->info).sampleSize) &&
((int)g_at3BitrateMeta[counter].dataByte == wave->sampleSizeMaybe)) {
(ctx->codec).unk40 = (char)g_at3BitrateMeta[counter].jointStereo;
(ctx->codec).unk41 = 0;
(ctx->codec).unk42 = 0;
(ctx->codec).unk43 = 0;
(ctx->codec).fmt.at3.formatByte1 = (char)g_at3BitrateMeta[counter].jointStereo;
(ctx->codec).fmt.at3.formatByte2 = 0;
(ctx->codec).fmt.at3.unk2a = 0;
(ctx->codec).fmt.at3.unk2b = 0;
return 0;
}
}
+122 -26
View File
@@ -35,22 +35,49 @@ std::map<u32, AudioDecoder *> g_audioDecoderContexts;
static bool oldStateLoaded = false;
static_assert(sizeof(SceAudiocodecCodec) == 128);
// Games allocate this structure and the ME writes into it, so every offset is load-bearing.
static_assert(offsetof(SceAudiocodecCodec, inBuf) == 0x18);
static_assert(offsetof(SceAudiocodecCodec, outBuf) == 0x20);
static_assert(offsetof(SceAudiocodecCodec, fmt) == 0x28);
static_assert(offsetof(SceAudiocodecCodec, fmt.at3.at3Related) == 0x30);
static_assert(offsetof(SceAudiocodecCodec, fmt.mp3.version) == 0x38);
static_assert(offsetof(SceAudiocodecCodec, fmt.mp3.bitrateIndex) == 0x44);
static_assert(offsetof(SceAudiocodecCodec, fmt.mp3.sampleRateIndex) == 0x48);
static_assert(offsetof(SceAudiocodecCodec, fmt.mp3.channelConfig) == 0x54);
static_assert(offsetof(SceAudiocodecCodec, allocMem) == 0x68);
// Notes on the codec-specific fields, from watching games and from reading the firmware modules
// that drive this interface (avcodec.prx, libatrac3plus.prx, libmp3.prx). See the union in
// sceAudiocodec.h for the layout.
//
// A general point worth knowing: the hardware never needs an exact input frame size here. The
// only thing sceAudiocodecDecode does with the size is a cache writeback over inBuf before
// handing the frame to the ME, so every "size" the firmware computes or stores is an upper
// bound.
// Atrac3+ (0x1000) frame sizes, and control bytes
//
// Bitrate Frame Size Byte 1 Byte 2 Channels
// -----------------------------------------------------
// 48kbps 0x118 0x24 0x22 1? // This hits "Frame data doesn't match channel configuration".
// 64kbps 0x178
// 64kbps 0x178 (0x2e implied by the formula below)
// 96kbps? 0x230 0x28 0x45 2
// 128kbps 0x2E8 0x28 0x5c 2
//
// Seems like maybe the frame size is equal to "Byte 2" * 8 + 8
// The frame size really is "Byte 2" * 8 + 8 - it holds for all three rows we have both numbers
// for, and libatrac3plus.prx writes 0x28/0x5c (the 128kbps row) into these two bytes at init as
// the worst case it sizes its EDRAM allocation against. So byte 2 is the hardware's own frame
// descriptor and reading it, as we do, is the right thing.
//
// Known byte values.
// The channel guess below (bit 3 of byte 1) fits both data points we have and nothing else.
// Atrac3 (0x1001)
//
// The Media Engine only ever sees the first 0x68 bytes of this structure - me_wrapper.prx does
// sceKernelDcacheWritebackInvalidateRange(ctx, 0x68) before handing it over, and avcodec.prx
// validates the same range - so whatever the hardware uses to size an Atrac3 frame has to be in
// there. The only Atrac3-specific thing anything writes is the joint-stereo flag in byte 1.
//
// Frame size data byte JointStereo?
// -------------------------------------------------
// 0x180 0x04 0
@@ -58,24 +85,36 @@ static_assert(sizeof(SceAudiocodecCodec) == 128);
// 0x0C0 0x0B 1
// 0x0C0 0x0E 0
// 0x098 0x0F 0
//
// NOTE: sceAudiocodecDecode below hardcodes 384 (0x180) bytes per frame for Atrac3, which is only
// the first row. If a game ever drives Atrac3 through sceAudiocodec at one of the other sizes we
// will decode garbage.
// AAC (0x1003)
// ------------------------------------------------
// Sample rate is at offset 0x28.
// srcBytesConsumed can be very small the first frames.
// 0x1000 is always the frame size.
// The firmware sizes AAC from the bytes at 0x2c and 0x2d rather than from 0x28: input is
// 0x600 or 0x609, output 0x1000 or 0x2000, depending on those two. Consistent with the above.
// MP3 (0x1002)
// ------------------------------------------------
// The parameters live at 0x38 (MPEG version index: 0 = MPEG2, 1 = MPEG1, 2 = MPEG2.5), 0x44
// (bitrate index) and 0x48 (sample rate index), and index the standard MPEG Layer III tables.
// sceAudiocodecInit presets 0x38 to 9999 meaning "not known yet", and GetInfo fills these in -
// which is why our GetInfo writes plausible values for a 128kbps 44.1kHz stereo stream.
// 0x54 is the channel configuration: libmp3.prx reads it as (value == 3) ? mono : stereo.
// 0x28 is not this frame's size - libmp3.prx writes 0x5A1 there once, the largest an MP3 frame
// can ever be. Output is 0x1200 bytes for MPEG1 (1152 samples) and 0x900 otherwise (576).
void CalculateInputBytesAndChannelsAt3Plus(const SceAudiocodecCodec *ctx, int *inputBytes, int *channels) {
*inputBytes = 0;
*channels = 2;
int size = ctx->unk41 * 8 + 8;
int size = ctx->fmt.at3.formatByte2 * 8 + 8;
// No idea if this is accurate, this is just a guess...
if (ctx->unk40 & 8) {
if (ctx->fmt.at3.formatByte1 & 8) {
*channels = 2;
} else {
*channels = 1;
@@ -93,6 +132,39 @@ void CalculateInputBytesAndChannelsAt3Plus(const SceAudiocodecCodec *ctx, int *i
}
}
// Atrac3 (0x1001). Unlike Atrac3+, the context doesn't carry a frame size - libatrac3plus.prx
// (and our mirror of it in AtracCtx2) writes only the joint-stereo flag into formatByte1 for
// Atrac3, so in general the size can't be recovered from the context alone.
//
// One case is unambiguous, though. Of the five frame sizes the hardware supports, exactly one is
// joint stereo (66kbps stereo, 0xC0 bytes), so that flag pins the size down by itself. Everything
// else falls back to the 132kbps stereo frame, which is what the old hardcoded 384 was.
static int Atrac3BytesPerFrameFromContext(const SceAudiocodecCodec *ctx, int *channels);
// The MPEG sample rates, indexed by [version][sampleRateIndex] exactly as the hardware's own
// table in avcodec.prx does. Version is 0 = MPEG2, 1 = MPEG1, 2 = MPEG2.5.
static const int g_mpegSampleRates[3][4] = {
{ 22050, 24000, 16000, 0 },
{ 44100, 48000, 32000, 0 },
{ 11025, 12000, 8000, 0 },
};
// Returns 0 if the context doesn't describe a rate we recognize - which includes the common case
// where sceAudiocodecInit has run but GetInfo hasn't filled the fields in yet (version is 9999).
static int Mp3SampleRateFromContext(const SceAudiocodecCodec *ctx) {
const int version = ctx->fmt.mp3.version;
const int index = ctx->fmt.mp3.sampleRateIndex;
if (version < 0 || version >= 3 || index < 0 || index >= 4) {
return 0;
}
return g_mpegSampleRates[version][index];
}
// libmp3.prx reads the channel configuration this way.
static int Mp3ChannelsFromContext(const SceAudiocodecCodec *ctx) {
return ctx->fmt.mp3.channelConfig == 3 ? 1 : 2;
}
// find the audio decoder for corresponding ctxPtr in audioList
static AudioDecoder *findDecoder(u32 ctxPtr) {
auto it = g_audioDecoderContexts.find(ctxPtr);
@@ -142,7 +214,7 @@ static int __AudioCodecInitCommon(u32 ctxPtr, int codec, bool mono) {
// Initialize the codec memory.
auto ctx = PSPPointer<SceAudiocodecCodec>::Create(ctxPtr);
ctx->unk_init = 0x5100601; // Firmware version indicator?
ctx->magic = 0x5100601;
ctx->err = 0;
int bytesPerFrame = 0;
@@ -156,7 +228,7 @@ static int __AudioCodecInitCommon(u32 ctxPtr, int codec, bool mono) {
case PSP_CODEC_MP3:
// Not seeing inited in Kurok (homebrew)
// _dbg_assert_(ctx->inited == 1);
ctx->mp3_9999 = 9999;
ctx->fmt.mp3.version = 9999;
break;
case PSP_CODEC_AAC:
// AAC / mp4
@@ -168,10 +240,9 @@ static int __AudioCodecInitCommon(u32 ctxPtr, int codec, bool mono) {
break;
case PSP_CODEC_AT3:
{
// See AtracBase::CreateDecoder. Need to properly understand this one day..
//
// TODO: How do we get the bytesPerFrame?
bytesPerFrame = 384; // TODO: Calculate from params.
// See AtracBase::CreateDecoder. The context only tells us whether the stream is joint
// stereo, which pins the frame size down in that one case - see the function.
bytesPerFrame = Atrac3BytesPerFrameFromContext(ctx, &channels);
bool jointStereo = IsAtrac3StreamJointStereo(PSP_CODEC_AT3, bytesPerFrame, channels);
// The only thing that changes are the jointStereo_ values.
extraData[0] = 1;
@@ -226,14 +297,23 @@ static int sceAudiocodecDecode(u32 ctxPtr, int codec) {
CalculateInputBytesAndChannelsAt3Plus(ctx, &bytesPerFrame, &channels);
break;
case PSP_CODEC_MP3:
bytesPerFrame = ctx->srcBytesRead;
// Not srcBytesRead - that's an output field holding what the *previous* call consumed.
// The hardware uses the bound at 0x28, which the caller also guarantees is readable at
// inBuf (it does a cache writeback over exactly that range), so it's the safe length to
// hand a decoder that parses the frame header itself.
bytesPerFrame = ctx->fmt.mp3.maxFrameBytes;
if (bytesPerFrame <= 0) {
bytesPerFrame = ctx->srcBytesRead;
}
channels = Mp3ChannelsFromContext(ctx);
sampleRate = Mp3SampleRateFromContext(ctx);
break;
case PSP_CODEC_AAC:
bytesPerFrame = ctx->srcBytesRead;
sampleRate = ctx->formatOutSamples;
sampleRate = ctx->fmt.aac.sampleRate;
break;
case PSP_CODEC_AT3:
bytesPerFrame = 384;
bytesPerFrame = Atrac3BytesPerFrameFromContext(ctx, &channels);
break;
}
@@ -254,7 +334,7 @@ static int sceAudiocodecDecode(u32 ctxPtr, int codec) {
int inDataConsumed = 0;
int outSamples = 0;
DEBUG_LOG(Log::ME, "decoder. in: %08x out: %08x unk40: %02x unk41: %02x", ctx->inBuf, ctx->outBuf, ctx->unk40, ctx->unk41);
DEBUG_LOG(Log::ME, "decoder. in: %08x out: %08x format: %02x %02x", ctx->inBuf, ctx->outBuf, ctx->fmt.at3.formatByte1, ctx->fmt.at3.formatByte2);
int16_t *outBuf = (int16_t *)Memory::GetPointerWriteOrException(ctx->outBuf);
@@ -285,14 +365,14 @@ static int sceAudiocodecGetInfo(u32 ctxPtr, int codec) {
// When this is called, the caller has written:
// * inptr
// * outptr
// * formatOutSamples = 0x5A1
// * 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.
ctx->mp3_3 = 3;
ctx->mp3_9 = 9;
ctx->mp3_0 = 0;
ctx->mp3_1 = 1;
ctx->mp3_1_first = 1;
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;
break;
}
@@ -314,9 +394,9 @@ static int sceAudiocodecCheckNeedMem(u32 ctxPtr, int codec) {
switch (codec) {
case 0x1000:
ctx->neededMem = 0x7bc0;
if (ctx->unk40 != 0x28 || ctx->unk41 != 0x5c) {
if (ctx->fmt.at3.formatByte1 != 0x28 || ctx->fmt.at3.formatByte2 != 0x5c) {
ctx->err = 0x20f;
return hleLogError(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "Bad format values: %02x %02x", ctx->unk40, ctx->unk41);
return hleLogError(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "Bad format values: %02x %02x", ctx->fmt.at3.formatByte1, ctx->fmt.at3.formatByte2);
}
break;
case 0x1001:
@@ -327,12 +407,12 @@ static int sceAudiocodecCheckNeedMem(u32 ctxPtr, int codec) {
break;
case 0x1003:
// Kosmodrones uses sceAudiocodec directly (no intermediate library).
INFO_LOG(Log::ME, "CheckNeedMem for codec %04x: format %02x %02x", codec, ctx->unk40, ctx->unk41);
INFO_LOG(Log::ME, "CheckNeedMem for codec %04x: format %02x %02x", codec, ctx->fmt.at3.formatByte1, ctx->fmt.at3.formatByte2);
break;
}
ctx->err = 0;
ctx->unk_init = 0x5100601;
ctx->magic = 0x5100601;
return hleLogWarning(Log::ME, 0, "%s", GetCodecName(codec));
}
@@ -399,6 +479,22 @@ static const At3HeaderMap at3HeaderMap[] = {
{ 0x00C0, 2, 1 }, // 66 kbps stereo
};
static int Atrac3BytesPerFrameFromContext(const SceAudiocodecCodec *ctx, int *channels) {
// AtracCtx2 puts the joint-stereo flag here for Atrac3, mirroring libatrac3plus.
const bool jointStereo = (ctx->fmt.at3.formatByte1 & 1) != 0;
if (jointStereo) {
for (size_t i = 0; i < ARRAY_SIZE(at3HeaderMap); ++i) {
if (at3HeaderMap[i].jointStereo) {
*channels = at3HeaderMap[i].channels;
return at3HeaderMap[i].bytes;
}
}
}
// 132kbps stereo - by far the most common, and what we assumed unconditionally before.
*channels = 2;
return 0x180;
}
bool IsAtrac3StreamJointStereo(int codecType, int bytesPerFrame, int channels) {
if (codecType != PSP_CODEC_AT3) {
// Well, might actually be, but it's not used in codec setup.
+83 -48
View File
@@ -21,66 +21,101 @@
class PointerWrap;
// audioType
// audioType. Every sceAudiocodec entry point validates these as "codec - 0x1000 < 6", so the
// range is exactly 0x1000..0x1005 - see avcodec.prx.
enum PSPAudioType {
PSP_CODEC_AT3PLUS = 0x00001000,
PSP_CODEC_AT3 = 0x00001001,
PSP_CODEC_MP3 = 0x00001002,
PSP_CODEC_AAC = 0x00001003, // sceMp4 decodes this in an mp4 container
// 0x1004 is handled by parts of the firmware but it's ultimately rejected by the ME code. Never implemented.
PSP_CODEC_UNKNOWN_1004 = 0x00001004,
PSP_CODEC_WMA = 0x00001005,
};
// The context the Media Engine works on. Games allocate this themselves and hand it over, so the
// layout is fixed - don't reorder anything here.
//
// Offsets 0x00..0x27 are identical for every codec, and sceVideocodec's context shares them too
// (mpeg.prx and videocodec_260.prx annotate the same fields), so this really is one ME "codec
// context" ABI. Everything from 0x28 on is per-codec, which is what the union models: each
// library writes a different set of fields there.
struct SceAudiocodecCodec {
s32 unk_init;
s32 unk4;
s32 err; // 8
s32 edramAddr; // c // presumably in ME memory?
s32 neededMem; // 10 // 0x102400 for Atrac3+
s32 inited; // 14
u32 inBuf; // 18 // Before decoding, set this to the start of the raw frame.
s32 srcBytesRead; // 1c
u32 outBuf; // 20 // This is where the decoded data is written.
s32 dstSamplesWritten; // 24
// Probably, from here on out is a union with different fields for different codecs.
union { // offset 40 / 0x28
u32 magic; // 0x00 set to 0x05100601 before every ME call
s32 unk4; // 0x04
s32 err; // 0x08
u32 edramAddr; // 0x0c in ME memory
s32 neededMem; // 0x10 0x102400 for Atrac3+
s32 inited; // 0x14
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
// Codec-specific, 0x28..0x67.
union {
// Atrac3plus and Atrac3. libatrac3plus.prx writes formatByte1/formatByte2 at init
// (0x28/0x5c for the worst case it sizes EDRAM against) and zeroes at3Related before
// every Atrac3plus decode.
struct {
s8 unk40; // 28 format or looping related . Aka tailrelated
s8 unk41; // 29 format or looping related . Aka tailflag
s8 unk42;
s8 unk43;
};
u32 formatOutSamples;
};
union { // offset 44 / 0x2C
u8 formatByte1; // 0x28 bit 3 appears to mean stereo
u8 formatByte2; // 0x29 frame size in bytes = formatByte2 * 8 + 8
u8 unk2a; // 0x2a
u8 unk2b; // 0x2b
u32 unk2c; // 0x2c
u32 at3Related; // 0x30 zeroed for Atrac3plus, left alone for Atrac3
s32 unk34; // 0x34
s32 unk38[12]; // 0x38..0x67
} at3;
// MP3. Field meanings are from avcodec.prx's frame-size calculator, which indexes the
// standard MPEG bitrate and sample-rate tables with them, and from libmp3.prx reading
// version and channelConfig back out.
struct {
u8 unk44;
s8 unk45;
s8 unk46;
s8 unk47;
};
// 0x28 libmp3.prx writes 0x5A1 here once at setup, and never per frame: it is the
// largest an MP3 frame can be (144 * 320000 / 32000 + 1 padding byte). The hardware
// only uses it as the length for a cache writeback over inBuf before handing the
// frame to the ME, so it is an upper bound rather than this frame's size.
u32 maxFrameBytes;
u32 unk2c; // 0x2c
s32 unk30; // 0x30
s32 unk34; // 0x34
// 0x38 MPEG version index: 0 = MPEG2, 1 = MPEG1, 2 = MPEG2.5. sceAudiocodecInit
// presets it to 9999 to mean "not known yet"; GetInfo fills it in.
s32 version;
// 0x3c Observed to be 3. avcodec's (unreachable) frame-size path treats this as the
// raw MPEG layer bits and only computes a size for 1 or 2, where 1 is Layer III -
// so 3 would fall through to its maximum-size fallback. Unexplained.
s32 unk3c;
s32 unk40; // 0x40
s32 bitrateIndex; // 0x44 index into the MPEG Layer III bitrate table (9 = 128kbps)
s32 sampleRateIndex; // 0x48 0..2 within the version's rates (0 = 44100 for MPEG1)
s32 unk4c; // 0x4c
s32 unk50; // 0x50
// 0x54 libmp3.prx reads this as: channels = (channelConfig == 3) ? 1 : 2.
s32 channelConfig;
s32 unk58; // 0x58
s32 unk5c; // 0x5c
s32 unk60; // 0x60
s32 unk64; // 0x64
} mp3;
// AAC. The hardware sizes its input from the byte at 0x2c and its output from the byte
// at 0x2d; the sample rate at 0x28 is our own observation from games.
struct {
s16 unk44_16;
s16 unk46_16;
};
u32 unk44_32;
};
s32 unk48; // 30 Atrac3 (non-+) related. Zero with Atrac3+.
s32 unk52; // 34
s32 mp3_9999; // 38 // unk56
s32 mp3_3; // unk60 gets the value 3
s32 unk64; // Atrac3+ size related
s32 mp3_9;
s32 mp3_0;
s32 unk76;
s32 unk80;
s32 mp3_1_first;
s32 unk88;
s32 unk92;
s32 mp3_1;
s32 unk100;
u32 allocMem; // 104
// make sure the size is 128
u8 unk[20];
u32 sampleRate; // 0x28
u8 unk2c; // 0x2c
u8 unk2d; // 0x2d
u8 unk2e; // 0x2e
u8 unk2f; // 0x2f
s32 unk30[14]; // 0x30..0x67
} aac;
u8 raw[0x40];
} fmt;
u32 allocMem; // 0x68 not hardware - where our GetEDRAM stores what it handed out
u8 unk[0x14]; // 0x6c
};
void __AudioCodecInit();