From 651a9720196b8f80e9df5500f60550cd034b869f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 2 Apr 2025 17:26:59 +0200 Subject: [PATCH] More sceAudioCodec implementation work. Atrac3+ partially works now. --- Core/HLE/sceAudiocodec.cpp | 105 +++++++++++++++++++++++---- Core/HLE/sceAudiocodec.h | 44 +++++++++-- Core/HLE/sceUtility.cpp | 7 ++ Core/HW/SimpleAudioDec.h | 11 +-- ext/at3_standalone/atrac3plusdec.cpp | 11 +-- 5 files changed, 137 insertions(+), 41 deletions(-) diff --git a/Core/HLE/sceAudiocodec.cpp b/Core/HLE/sceAudiocodec.cpp index 16e8798840..78e0bd2d66 100644 --- a/Core/HLE/sceAudiocodec.cpp +++ b/Core/HLE/sceAudiocodec.cpp @@ -36,6 +36,50 @@ static bool oldStateLoaded = false; static_assert(sizeof(SceAudiocodecCodec) == 128); +// 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 +// 96kbps? 0x230 0x28 0x45 2 +// 128kbps 0x2E8 0x28 0x5c 2 +// +// Seems like maybe the frame size is equal to "Byte 2" * 8 + 8 +// +// Known byte values. + +void CalculateInputBytesAndChannels(const SceAudiocodecCodec *ctx, int codec, int *inputBytes, int *channels) { + *inputBytes = 0; + *channels = 2; + switch (codec) { + case PSP_CODEC_AT3PLUS: + { + int size = ctx->unk41 * 8 + 8; + // No idea if this is accurate, this is just a guess... + if (ctx->unk40 & 8) { + *channels = 2; + } else { + *channels = 1; + } + switch (size) { + case 0x118: + case 0x178: + case 0x230: + case 0x2E8: + // These have been seen before, let's return it. + *inputBytes = size; + return; + default: + break; + } + } + default: + // Unsupported codec, ignore. + break; + } +} + // find the audio decoder for corresponding ctxPtr in audioList static AudioDecoder *findDecoder(u32 ctxPtr) { auto it = g_audioDecoderContexts.find(ctxPtr); @@ -93,10 +137,19 @@ static int __AudioCodecInitCommon(u32 ctxPtr, int codec, bool mono) { WARN_LOG_REPORT(Log::HLE, "sceAudiocodecInit(%08x, %d): replacing existing context", ctxPtr, codec); } - AudioDecoder *decoder = CreateAudioDecoder(audioType); - decoder->SetCtxPtr(ctxPtr); - g_audioDecoderContexts[ctxPtr] = decoder; + int inFrameBytes; + int channels; + CalculateInputBytesAndChannels(ctx, codec, &inFrameBytes, &channels); + if (inFrameBytes) { + INFO_LOG(Log::ME, "sceAudioDecoder: Creating codec with %04x frame size and %d channels, codec %04x", inFrameBytes, channels, codec); + AudioDecoder *decoder = CreateAudioDecoder(audioType, 44100, channels, inFrameBytes); + decoder->SetCtxPtr(ctxPtr); + g_audioDecoderContexts[ctxPtr] = decoder; + } else { + ERROR_LOG(Log::ME, "sceAudioDecoder: Unsupported codec %08x", codec); + g_audioDecoderContexts[ctxPtr] = nullptr; + } return hleLogDebug(Log::ME, 0); } @@ -119,33 +172,42 @@ static int sceAudiocodecDecode(u32 ctxPtr, int codec) { return hleLogError(Log::ME, 0, "UNIMPL sceAudiocodecDecode(%08x, %i (%s))", ctxPtr, codec, GetCodecName(codec)); } + // TODO: Should check that codec corresponds to the currently used codec in the context, I guess.. + + auto ctx = PSPPointer::Create(ctxPtr); // On game-owned heap, no need to allocate. + int inFrameBytes; + int channels; + CalculateInputBytesAndChannels(ctx, codec, &inFrameBytes, &channels); + // find a decoder in audioList auto decoder = findDecoder(ctxPtr); if (!decoder && oldStateLoaded) { // We must have loaded an old state that did not have sceAudiocodec information. // Fake it by creating the desired context. - decoder = CreateAudioDecoder(audioType); + decoder = CreateAudioDecoder(audioType, 44100, channels, inFrameBytes); decoder->SetCtxPtr(ctxPtr); g_audioDecoderContexts[ctxPtr] = decoder; } if (decoder) { // Use SimpleAudioDec to decode audio - auto ctx = PSPPointer::Create(ctxPtr); // On game-owned heap, no need to allocate. // Decode audio int inDataConsumed = 0; int outSamples = 0; - INFO_LOG(Log::ME, "decoder. in: %08x out: %08x format1: %d format2: %d", ctx->inBuf, ctx->outBuf, ctx->format1, ctx->format2); + INFO_LOG(Log::ME, "decoder. in: %08x out: %08x unk40: %d unk41: %d", ctx->inBuf, ctx->outBuf, ctx->unk40, ctx->unk41); int16_t *outBuf = (int16_t *)Memory::GetPointerWrite(ctx->outBuf); - bool result = decoder->Decode(Memory::GetPointer(ctx->inBuf), ctx->srcFrameSize, &inDataConsumed, 2, outBuf, &outSamples); + bool result = decoder->Decode(Memory::GetPointer(ctx->inBuf), inFrameBytes, &inDataConsumed, 2, outBuf, &outSamples); if (!result) { ctx->err = 0x20b; ERROR_LOG(Log::ME, "AudioCodec decode failed. Setting error to %08x", ctx->err); } + + ctx->srcBytesRead = inDataConsumed; + ctx->dstSamplesWritten = outSamples; } return hleLogInfo(Log::ME, 0, "codec %s", GetCodecName(codec)); } @@ -166,23 +228,28 @@ static int sceAudiocodecCheckNeedMem(u32 ctxPtr, int codec) { // Check for expected values. auto ctx = PSPPointer::Create(ctxPtr); // On game-owned heap, no need to allocate. - if (ctx->format1 != 0x28 || ctx->format2 != 0x5c) { - ctx->err = 0x20f; - return hleLogError(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "Bad format values"); - } - ctx->unk_init = 0x5100601; - ctx->err = 0; switch (codec) { - case 0x1000: // at3+ + case 0x1000: ctx->neededMem = 0x7bc0; + if (ctx->unk40 != 0x28 || ctx->unk41 != 0x5c) { + ctx->err = 0x20f; + return hleLogError(Log::ME, SCE_AVCODEC_ERROR_INVALID_DATA, "Bad format values: %02x %02x", ctx->unk40, ctx->unk41); + } break; case 0x1001: ctx->neededMem = 0x3de0; break; + case 0x1003: + // Kosmodrones uses sceAudiocodec directly (no intermediate library). + INFO_LOG(Log::ME, "CheckNeedMem for codec %04x: format %02x %02x", ctx->unk40, ctx->unk41); + break; } - return hleLogWarning(Log::ME, 0, "UNIMPL sceAudiocodecCheckNeedMem(%08x, %i (%s))", ctxPtr, codec, GetCodecName(codec)); + ctx->err = 0; + ctx->unk_init = 0x5100601; + + return hleLogWarning(Log::ME, 0, "%s", GetCodecName(codec)); } static int sceAudiocodecGetEDRAM(u32 ctxPtr, int codec) { @@ -200,7 +267,13 @@ static int sceAudiocodecReleaseEDRAM(u32 ctxPtr, int id) { return hleLogWarning(Log::ME, 0, "failed to remove decoder"); } -static int sceAudiocodecGetOutputBytes(u32 ctxPtr, int id) { +static int sceAudiocodecGetOutputBytes(u32 ctxPtr, int codec) { + switch (codec) { + case 0x1000: return hleLogInfo(Log::ME, 0x2000); // Atrac3+ + case 0x1001: return hleLogInfo(Log::ME, 0x1000); // Atrac3 + default: + return hleLogWarning(Log::ME, 0, "Block size query not implemented for codec %04x", codec); + } return hleLogInfo(Log::ME, 0); } diff --git a/Core/HLE/sceAudiocodec.h b/Core/HLE/sceAudiocodec.h index b4a7662fb3..6162ddad0e 100644 --- a/Core/HLE/sceAudiocodec.h +++ b/Core/HLE/sceAudiocodec.h @@ -21,6 +21,15 @@ class PointerWrap; +// audioType +enum PSPAudioType { + PSP_CODEC_AT3PLUS = 0x00001000, + PSP_CODEC_AT3 = 0x00001001, + PSP_CODEC_MP3 = 0x00001002, + PSP_CODEC_AAC = 0x00001003, + PSP_CODEC_WMA = 0x00001005, +}; + struct SceAudiocodecCodec { s32 unk_init; s32 unk4; @@ -29,18 +38,37 @@ struct SceAudiocodecCodec { s32 neededMem; // 10 // 0x102400 for Atrac3+ s32 inited; // 14 u32 inBuf; // 18 // Before decoding, set this to the start of the raw frame. - s32 srcFrameSize; // 1c + s32 srcBytesRead; // 1c u32 outBuf; // 20 // This is where the decoded data is written. - s32 dstBytesWritten; // 24 - s8 format1; // 28 format or looping related // Probably, from here on out is a union with different fields for different codecs. - s8 format2; // 29 format or looping related - s16 unk42; // 2a - u32 unk44; // 2c + s32 dstSamplesWritten; // 24 + // Probably, from here on out is a union with different fields for different codecs. + union { // offset 40 / 0x28 + struct { + s8 unk40; // 28 format or looping related + s8 unk41; // 29 format or looping related + s8 unk42; + s8 unk43; + }; + u32 formatOutSamples; + }; + union { // offset 44 / 0x2C + struct { + u8 unk44; + s8 unk45; + s8 unk46; + s8 unk47; + }; + 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 + s32 mp3_9999; // 38 // unk56 s32 unk60; - s32 unk64; + s32 unk64; // Atrac3+ size related s32 unk68; s32 unk72; s32 unk76; diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index 108e2e6391..00a0e20650 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -83,6 +83,13 @@ static void NotifyLoadStatusAvcodec(int state, u32 loadAddr, u32 totalSize) { static void NotifyLoadStatusAtrac(int state, u32 loadAddr, u32 totalSize) { if (state == 1) { + // If HLE of sceAtrac is disabled, things will break! + // For now we do angry logging and a debug assert. + if ((DisableHLEFlags)g_Config.iDisableHLE & DisableHLEFlags::sceAtrac) { + ERROR_LOG(Log::ME, "sceAtrac HLE is disabled, and the game tries to load sceAtrac from firmware - this won't work!"); + _dbg_assert_(false); + } + // We try to imitate a recent version of the prx. // Let's just give it a piece of the space. constexpr int version = 0x105; // latest. diff --git a/Core/HW/SimpleAudioDec.h b/Core/HW/SimpleAudioDec.h index 9884821361..bc827bd431 100644 --- a/Core/HW/SimpleAudioDec.h +++ b/Core/HW/SimpleAudioDec.h @@ -18,18 +18,11 @@ #pragma once #include "Core/HW/MediaEngine.h" -#include "Core/HLE/sceAudio.h" +#include "Core/HLE/sceAudiocodec.h" // Decodes packet by packet - does NOT demux. -// audioType -enum PSPAudioType { - PSP_CODEC_AT3PLUS = 0x00001000, - PSP_CODEC_AT3 = 0x00001001, - PSP_CODEC_MP3 = 0x00001002, - PSP_CODEC_AAC = 0x00001003, -}; - +// This basically corresponds to the core of sceAudiocodec. class AudioDecoder { public: virtual ~AudioDecoder() {} diff --git a/ext/at3_standalone/atrac3plusdec.cpp b/ext/at3_standalone/atrac3plusdec.cpp index e5eaae9f1a..bf37fac83a 100644 --- a/ext/at3_standalone/atrac3plusdec.cpp +++ b/ext/at3_standalone/atrac3plusdec.cpp @@ -329,14 +329,9 @@ int atrac3p_decode_frame(ATRAC3PContext *ctx, float *out_data[2], int *nb_sample if (ch_block >= ctx->num_channel_blocks || ctx->channel_blocks[ch_block] != ch_unit_id) { av_log(AV_LOG_WARNING, "Frame data doesn't match channel configuration! ch_block %d >= num_channel_blocks %d", ch_block, ctx->num_channel_blocks); - // This happens in Code Lyoko, see issue #19994. - // The atrac3+ wav header clearly specifies stereo, so we only have one channel block, but here we try to decode a third (and/or fourth) one. - // Maybe the data chunk is just improperly terminated, but it's reported that when ignoring this error previously, - // it didn't sound right. So I'm really not sure what's going on here. Most likely some unknown feature of the Atrac3+ format that we - // failed to parse previously? - // - // return AVERROR_INVALIDDATA; // This soft-locks the game. - return FFMIN(ctx->block_align, indata_size); // We try to stumble along. + // We used to have trouble in Code Lyoko and other games here. It's usually because data is corrupted + // or the wrong channel configuration is used.. + return AVERROR_INVALIDDATA; } ctx->ch_units[ch_block].unit_type = ch_unit_id;