From 2c482a48b22e30209f8ed25d6e3b20598ce95b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 7 Sep 2026 11:44:09 -0600 Subject: [PATCH 1/3] sceAudiocodec: name the codec context fields, and make the tail a union The first 0x28 bytes of the context are the same for every codec - and for sceVideocodec's own context, which annotates the same fields - so this is one ME codec-context ABI. Everything after that is per-codec, with each library writing a different set of fields, so it becomes a union. Also documents that 0x28 is not a frame size for MP3: the hardware only uses it as a cache-writeback length, so it is an upper bound - which is why the firmware never bothers computing an exact one anywhere. Adds codec id 0x1004, which the hardware accepts and handles much like MP3. Unidentified, but the range check really does accept six codecs. Co-Authored-By: Claude Opus 5 (1M context) --- Core/HLE/AtracCtx2.cpp | 14 ++-- Core/HLE/sceAudiocodec.cpp | 74 ++++++++++++++------ Core/HLE/sceAudiocodec.h | 134 ++++++++++++++++++++++++------------- 3 files changed, 147 insertions(+), 75 deletions(-) diff --git a/Core/HLE/AtracCtx2.cpp b/Core/HLE/AtracCtx2.cpp index 3dccd9cb65..644b2e67b0 100644 --- a/Core/HLE/AtracCtx2.cpp +++ b/Core/HLE/AtracCtx2.cpp @@ -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; } } diff --git a/Core/HLE/sceAudiocodec.cpp b/Core/HLE/sceAudiocodec.cpp index 9bcd2f2363..70ef3563de 100644 --- a/Core/HLE/sceAudiocodec.cpp +++ b/Core/HLE/sceAudiocodec.cpp @@ -35,19 +35,41 @@ std::map 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) // @@ -58,24 +80,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; @@ -142,7 +176,7 @@ static int __AudioCodecInitCommon(u32 ctxPtr, int codec, bool mono) { // Initialize the codec memory. auto ctx = PSPPointer::Create(ctxPtr); - ctx->unk_init = 0x5100601; // Firmware version indicator? + ctx->magic = 0x5100601; ctx->err = 0; int bytesPerFrame = 0; @@ -156,7 +190,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 @@ -230,7 +264,7 @@ static int sceAudiocodecDecode(u32 ctxPtr, int codec) { break; case PSP_CODEC_AAC: bytesPerFrame = ctx->srcBytesRead; - sampleRate = ctx->formatOutSamples; + sampleRate = ctx->fmt.aac.sampleRate; break; case PSP_CODEC_AT3: bytesPerFrame = 384; @@ -254,7 +288,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 +319,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 +348,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 +361,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)); } diff --git a/Core/HLE/sceAudiocodec.h b/Core/HLE/sceAudiocodec.h index ac3aaf9c45..e20565fc91 100644 --- a/Core/HLE/sceAudiocodec.h +++ b/Core/HLE/sceAudiocodec.h @@ -21,66 +21,104 @@ 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 accepted by the hardware and handled much like MP3 (it shares MP3's frame-size + // calculator, with the parameter fields at different offsets), but nothing seen so far uses + // it and we don't know what it is. MPEG Layer II is a guess. Not a gap in the enum: the + // range check above really does accept six values. + 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(); From 53eb468a28ab990e4f3ab6f335cbe2af0eb89d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 7 Sep 2026 11:54:35 -0600 Subject: [PATCH 2/3] sceAudiocodec: use the codec context for Atrac3 and MP3 instead of guessing Atrac3 no longer hardcodes 384 bytes per frame. The context only carries the joint-stereo flag for Atrac3 - libatrac3plus.prx writes nothing else there, and AtracCtx2 already mirrors that - but exactly one of the five supported frame sizes is joint stereo (66kbps stereo, 0xC0 bytes), so that flag identifies it on its own. Everything else keeps the old 132kbps assumption, now via the existing at3HeaderMap rather than a magic number. MP3 was passing srcBytesRead as the input length, which is an output field holding what the *previous* call consumed - zero on the first frame. Use the bound at 0x28 instead, which is what the hardware uses and which the caller guarantees is readable at inBuf, since it does a cache writeback over exactly that range. Channels and sample rate now come from the context's channel configuration and its version/sample-rate index pair, using the same table avcodec.prx indexes, rather than being assumed stereo 44100. Co-Authored-By: Claude Opus 5 (1M context) --- Core/HLE/sceAudiocodec.cpp | 69 ++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/Core/HLE/sceAudiocodec.cpp b/Core/HLE/sceAudiocodec.cpp index 70ef3563de..f8b888c69c 100644 --- a/Core/HLE/sceAudiocodec.cpp +++ b/Core/HLE/sceAudiocodec.cpp @@ -127,6 +127,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); @@ -202,10 +235,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; @@ -260,14 +292,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->fmt.aac.sampleRate; break; case PSP_CODEC_AT3: - bytesPerFrame = 384; + bytesPerFrame = Atrac3BytesPerFrameFromContext(ctx, &channels); break; } @@ -433,6 +474,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. From 909673f4ee6b4653fad86a8c0574b7c1a3f8efd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 7 Sep 2026 12:04:58 -0600 Subject: [PATCH 3/3] sceAudiocodec: correct the 0x1004 note, record the ME's 0x68-byte view me_wrapper.prx's dispatch table gives 0x1004 a handler that returns -1, so it is plumbed through avcodec.prx but not implemented on 6.61 - not the real sixth codec the earlier comment claimed. Also records the bound that matters for the Atrac3 frame-size question: the ME is handed a context whose first 0x68 bytes are the only ones made coherent, so nothing outside that can be reaching it. Co-Authored-By: Claude Opus 5 (1M context) --- Core/HLE/sceAudiocodec.cpp | 5 +++++ Core/HLE/sceAudiocodec.h | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Core/HLE/sceAudiocodec.cpp b/Core/HLE/sceAudiocodec.cpp index f8b888c69c..49782e5232 100644 --- a/Core/HLE/sceAudiocodec.cpp +++ b/Core/HLE/sceAudiocodec.cpp @@ -73,6 +73,11 @@ static_assert(offsetof(SceAudiocodecCodec, allocMem) == 0x68); // 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 diff --git a/Core/HLE/sceAudiocodec.h b/Core/HLE/sceAudiocodec.h index e20565fc91..1da7067f31 100644 --- a/Core/HLE/sceAudiocodec.h +++ b/Core/HLE/sceAudiocodec.h @@ -28,10 +28,7 @@ enum PSPAudioType { PSP_CODEC_AT3 = 0x00001001, PSP_CODEC_MP3 = 0x00001002, PSP_CODEC_AAC = 0x00001003, // sceMp4 decodes this in an mp4 container - // 0x1004 is accepted by the hardware and handled much like MP3 (it shares MP3's frame-size - // calculator, with the parameter fields at different offsets), but nothing seen so far uses - // it and we don't know what it is. MPEG Layer II is a guess. Not a gap in the enum: the - // range check above really does accept six values. + // 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, };