From e70dd3b2dfbd89c904a6f794549f709a8aef31e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 19 Mar 2025 16:15:51 +0100 Subject: [PATCH] Break out AtracTrack into its own file. Add a little atrac analysis tool to ImDebugger (for future use) --- CMakeLists.txt | 2 + Common/System/Request.h | 1 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 + Core/HLE/AtracCtx.cpp | 285 -------------------------- Core/HLE/AtracCtx.h | 103 ---------- Core/HLE/AtracCtx2.cpp | 2 +- Core/HLE/sceAtrac.cpp | 98 +++++---- Core/HLE/sceAtrac.h | 9 +- Core/Util/AtracTrack.cpp | 300 ++++++++++++++++++++++++++++ Core/Util/AtracTrack.h | 114 +++++++++++ Qt/QtMain.cpp | 3 + UI/DarwinFileSystemServices.mm | 3 + UI/ImDebugger/ImDebugger.cpp | 48 ++++- UI/ImDebugger/ImDebugger.h | 13 ++ UWP/CoreUWP/CoreUWP.vcxproj | 4 +- UWP/CoreUWP/CoreUWP.vcxproj.filters | 8 +- UWP/PPSSPP_UWPMain.cpp | 5 +- Windows/main.cpp | 2 + android/jni/Android.mk | 1 + libretro/Makefile.common | 5 +- 21 files changed, 571 insertions(+), 443 deletions(-) create mode 100644 Core/Util/AtracTrack.cpp create mode 100644 Core/Util/AtracTrack.h diff --git a/CMakeLists.txt b/CMakeLists.txt index de86641d03..bcd937741a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2413,6 +2413,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/System.h Core/ThreadPools.cpp Core/ThreadPools.h + Core/Util/AtracTrack.cpp + Core/Util/AtracTrack.h Core/Util/AudioFormat.cpp Core/Util/AudioFormat.h Core/Util/GameManager.cpp diff --git a/Common/System/Request.h b/Common/System/Request.h index f3b5547a5f..5e843c9546 100644 --- a/Common/System/Request.h +++ b/Common/System/Request.h @@ -104,6 +104,7 @@ enum class BrowseFileType { SOUND_EFFECT, ZIP, SYMBOL_MAP, + ATRAC3, ANY, }; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 94f7e4e92a..c281c4ba59 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -1086,6 +1086,7 @@ + @@ -1471,6 +1472,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 18d7b6dd6c..eb4f5f6c57 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1342,6 +1342,9 @@ HLE\Libraries + + Util + @@ -2166,6 +2169,9 @@ HLE + + Util + diff --git a/Core/HLE/AtracCtx.cpp b/Core/HLE/AtracCtx.cpp index a904fa4baf..3b0e73bcdc 100644 --- a/Core/HLE/AtracCtx.cpp +++ b/Core/HLE/AtracCtx.cpp @@ -32,13 +32,6 @@ const size_t overAllocBytes = 16384; -const int RIFF_CHUNK_MAGIC = 0x46464952; -const int RIFF_WAVE_MAGIC = 0x45564157; -const int FMT_CHUNK_MAGIC = 0x20746D66; -const int DATA_CHUNK_MAGIC = 0x61746164; -const int SMPL_CHUNK_MAGIC = 0x6C706D73; -const int FACT_CHUNK_MAGIC = 0x74636166; - Atrac::~Atrac() { ResetData(); } @@ -238,284 +231,6 @@ void Track::DebugLog() const { DEBUG_LOG(Log::ME, "sampleSize: %d (%03x", bytesPerFrame, bytesPerFrame); } -int AnalyzeAtracTrack(u32 addr, u32 size, Track *track) { - // 72 is about the size of the minimum required data to even be valid. - - // TODO: Validate stuff more. - if (Memory::ReadUnchecked_U32(addr) != RIFF_CHUNK_MAGIC) { - ERROR_LOG(Log::ME, "Couldn't find RIFF header"); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - - struct RIFFFmtChunk { - u16_le fmtTag; - u16_le channels; - u32_le samplerate; - u32_le avgBytesPerSec; - u16_le blockAlign; - }; - - u32 offset = 8; - track->firstSampleOffset = 0; - - while (Memory::Read_U32(addr + offset) != RIFF_WAVE_MAGIC) { - // Get the size preceding the magic. - int chunk = Memory::Read_U32(addr + offset - 4); - // Round the chunk size up to the nearest 2. - offset += chunk + (chunk & 1); - if (offset + 12 > size) { - ERROR_LOG(Log::ME, "AnalyzeAtracTrack(%08x, %d): too small for WAVE chunk at offset %d", addr, size, offset); - return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; - } - if (Memory::Read_U32(addr + offset) != RIFF_CHUNK_MAGIC) { - ERROR_LOG(Log::ME, "AnalyzeAtracTrack(%08x, %d): RIFF chunk did not contain WAVE", addr, size); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - offset += 8; - } - offset += 4; - - if (offset != 12) { - WARN_LOG(Log::ME, "RIFF chunk at offset: %d", offset); - } - - // RIFF size excluding chunk header. - track->fileSize = Memory::Read_U32(addr + offset - 8) + 8; - - // Even if the RIFF size is too low, it may simply be incorrect. This works on real firmware. - u32 maxSize = std::max(track->fileSize, size); - - bool bfoundData = false; - u32 dataChunkSize = 0; - int sampleOffsetAdjust = 0; - - while (maxSize >= offset + 8 && !bfoundData) { - int chunkMagic = Memory::Read_U32(addr + offset); - u32 chunkSize = Memory::Read_U32(addr + offset + 4); - // Account for odd sized chunks. - if (chunkSize & 1) { - WARN_LOG_REPORT_ONCE(oddchunk, Log::ME, "RIFF chunk had uneven size"); - } - chunkSize += (chunkSize & 1); - offset += 8; - if (chunkSize > maxSize - offset) - break; - switch (chunkMagic) { - case FMT_CHUNK_MAGIC: - { - if (track->codecType != 0) { - ERROR_LOG(Log::ME, "AnalyzeTrack: multiple fmt chunks is not valid"); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - - auto at3fmt = PSPPointer::Create(addr + offset); - if (chunkSize < 32 || (at3fmt->fmtTag == AT3_PLUS_MAGIC && chunkSize < 52)) { - ERROR_LOG(Log::ME, "AnalyzeTrack: fmt definition too small(%d)", chunkSize); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - - if (at3fmt->fmtTag == AT3_MAGIC) - track->codecType = PSP_MODE_AT_3; - else if (at3fmt->fmtTag == AT3_PLUS_MAGIC) - track->codecType = PSP_MODE_AT_3_PLUS; - else { - ERROR_LOG(Log::ME, "AnalyzeTrack: invalid fmt magic: %04x", at3fmt->fmtTag); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - track->channels = at3fmt->channels; - if (track->channels != 1 && track->channels != 2) { - ERROR_LOG_REPORT(Log::ME, "AnalyzeTrack: unsupported channel count %d", track->channels); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - if (at3fmt->samplerate != 44100) { - ERROR_LOG_REPORT(Log::ME, "AnalyzeTrack: unsupported sample rate %d", at3fmt->samplerate); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - track->bitrate = at3fmt->avgBytesPerSec * 8; - track->bytesPerFrame = at3fmt->blockAlign; - if (track->bytesPerFrame == 0) { - ERROR_LOG_REPORT(Log::ME, "invalid bytes per frame: %d", track->bytesPerFrame); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - - // TODO: There are some format specific bytes here which seem to have fixed values? - // Probably don't need them. - - if (at3fmt->fmtTag == AT3_MAGIC) { - // This is the offset to the jointStereo_ field. - track->jointStereo = Memory::Read_U16(addr + offset + 24); - // Then there are more fields here. - u16 unknown1_2 = Memory::Read_U16(addr + offset + 30); - - } - if (chunkSize > 16) { - // Read and format extra bytes as hexadecimal - std::string hex; - DataToHexString(Memory::GetPointer(addr + offset + 16), chunkSize - 16, &hex, false); - DEBUG_LOG(Log::ME, "Additional chunk data (beyond 16 bytes): %s", hex.c_str()); - } - break; - } - case FACT_CHUNK_MAGIC: - { - track->endSample = Memory::Read_U32(addr + offset); - if (chunkSize >= 8) { - track->firstSampleOffset = Memory::Read_U32(addr + offset + 4); - } - if (chunkSize >= 12) { - u32 largerOffset = Memory::Read_U32(addr + offset + 8); - // Works, but "largerOffset"?? - sampleOffsetAdjust = track->firstSampleOffset - largerOffset; - } - break; - } - case SMPL_CHUNK_MAGIC: - { - if (chunkSize < 32) { - ERROR_LOG(Log::ME, "AnalyzeTrack: smpl chunk too small (%d)", chunkSize); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - int checkNumLoops = Memory::Read_U32(addr + offset + 28); - if (checkNumLoops != 0 && chunkSize < 36 + 20) { - ERROR_LOG(Log::ME, "AnalyzeTrack: smpl chunk too small for loop (%d, %d)", checkNumLoops, chunkSize); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - if (checkNumLoops < 0) { - ERROR_LOG(Log::ME, "bad checkNumLoops (%d)", checkNumLoops); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - - track->loopinfo.resize(checkNumLoops); - u32 loopinfoAddr = addr + offset + 36; - // The PSP only cares about the first loop start and end, it seems. - // Most likely can skip the rest of this data, but it's not hurting anyone. - for (int i = 0; i < checkNumLoops && 36 + (u32)i < chunkSize; i++, loopinfoAddr += 24) { - track->loopinfo[i].cuePointID = Memory::Read_U32(loopinfoAddr); - track->loopinfo[i].type = Memory::Read_U32(loopinfoAddr + 4); - track->loopinfo[i].startSample = Memory::Read_U32(loopinfoAddr + 8); - track->loopinfo[i].endSample = Memory::Read_U32(loopinfoAddr + 12); - track->loopinfo[i].fraction = Memory::Read_U32(loopinfoAddr + 16); - track->loopinfo[i].playCount = Memory::Read_U32(loopinfoAddr + 20); - - if (i == 0 && track->loopinfo[i].startSample >= track->loopinfo[i].endSample) { - ERROR_LOG(Log::ME, "AnalyzeTrack: loop starts after it ends"); - return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS; - } - } - break; - } - case DATA_CHUNK_MAGIC: - { - bfoundData = true; - track->dataByteOffset = offset; - dataChunkSize = chunkSize; - if (track->fileSize < offset + chunkSize) { - WARN_LOG_REPORT(Log::ME, "Atrac data chunk extends beyond riff chunk"); - track->fileSize = offset + chunkSize; - } - } - break; - } - offset += chunkSize; - } - - if (track->codecType == 0) { - ERROR_LOG(Log::ME, "AnalyzeTrack: Could not detect codec"); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - - if (!bfoundData) { - ERROR_LOG(Log::ME, "AnalyzeTrack: No data chunk found"); - return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; - } - - // set the loopStartSample_ and loopEndSample_ by loopinfo_ - if (track->loopinfo.size() > 0) { - track->loopStartSample = track->loopinfo[0].startSample + track->FirstOffsetExtra() + sampleOffsetAdjust; - track->loopEndSample = track->loopinfo[0].endSample + track->FirstOffsetExtra() + sampleOffsetAdjust; - } else { - track->loopStartSample = -1; - track->loopEndSample = -1; - } - - // if there is no correct endsample, try to guess it - if (track->endSample <= 0 && track->bytesPerFrame != 0) { - track->endSample = (dataChunkSize / track->bytesPerFrame) * track->SamplesPerFrame(); - track->endSample -= track->FirstSampleOffsetFull(); - } - track->endSample -= 1; - - if (track->loopEndSample != -1 && track->loopEndSample > track->endSample + track->FirstSampleOffsetFull()) { - ERROR_LOG(Log::ME, "AnalyzeTrack: loop after end of data"); - return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS; - } - - return 0; -} - -int AnalyzeAA3Track(u32 addr, u32 size, u32 fileSize, Track *track) { - if (size < 10) { - return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL; - } - // TODO: Make sure this validation is correct, more testing. - - const u8 *buffer = Memory::GetPointer(addr); - if (buffer[0] != 'e' || buffer[1] != 'a' || buffer[2] != '3') { - return SCE_ERROR_ATRAC_AA3_INVALID_DATA; - } - - // It starts with an id3 header (replaced with ea3.) This is the size. - u32 tagSize = buffer[9] | (buffer[8] << 7) | (buffer[7] << 14) | (buffer[6] << 21); - if (size < tagSize + 36) { - return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL; - } - - // EA3 header starts at id3 header (10) + tagSize. - buffer = Memory::GetPointer(addr + 10 + tagSize); - if (buffer[0] != 'E' || buffer[1] != 'A' || buffer[2] != '3') { - ERROR_LOG(Log::ME, "AnalyzeAA3Track: Invalid EA3 magic bytes"); - return SCE_ERROR_ATRAC_AA3_INVALID_DATA; - } - - track->fileSize = fileSize; - - // Based on FFmpeg's code. - u32 codecParams = buffer[33] | (buffer[34] << 8) | (buffer[35] << 16); - const u32 at3SampleRates[8] = { 32000, 44100, 48000, 88200, 96000, 0 }; - - switch (buffer[32]) { - case 0: - track->codecType = PSP_MODE_AT_3; - track->bytesPerFrame = (codecParams & 0x03FF) * 8; - track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 1024; - track->channels = 2; - track->jointStereo = (codecParams >> 17) & 1; - break; - case 1: - track->codecType = PSP_MODE_AT_3_PLUS; - track->bytesPerFrame = ((codecParams & 0x03FF) * 8) + 8; - track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 2048; - track->channels = (codecParams >> 10) & 7; - break; - case 3: - case 4: - case 5: - ERROR_LOG(Log::ME, "AnalyzeAA3Track: unsupported codec type %d", buffer[32]); - return SCE_ERROR_ATRAC_AA3_INVALID_DATA; - default: - ERROR_LOG(Log::ME, "AnalyzeAA3Track: invalid codec type %d", buffer[32]); - return SCE_ERROR_ATRAC_AA3_INVALID_DATA; - } - - track->dataByteOffset = 10 + tagSize + 96; - track->firstSampleOffset = 0; - if (track->endSample < 0 && track->bytesPerFrame != 0) { - track->endSample = ((track->fileSize - track->dataByteOffset) / track->bytesPerFrame) * track->SamplesPerFrame(); - } - track->endSample -= 1; - return 0; -} - int Atrac::GetSoundSample(int *endSample, int *loopStartSample, int *loopEndSample) const { *endSample = track_.endSample; *loopStartSample = track_.loopStartSample == -1 ? -1 : track_.loopStartSample - track_.FirstSampleOffsetFull(); diff --git a/Core/HLE/AtracCtx.h b/Core/HLE/AtracCtx.h index 4221525d47..5d80daa909 100644 --- a/Core/HLE/AtracCtx.h +++ b/Core/HLE/AtracCtx.h @@ -37,10 +37,6 @@ struct AtracResetBufferInfo { AtracSingleResetBufferInfo second; }; -#define AT3_MAGIC 0x0270 -#define AT3_PLUS_MAGIC 0xFFFE -#define PSP_MODE_AT_3_PLUS 0x00001000 -#define PSP_MODE_AT_3 0x00001001 const int PSP_ATRAC_ALLDATA_IS_ON_MEMORY = -1; const int PSP_ATRAC_NONLOOP_STREAM_DATA_IS_ON_MEMORY = -2; @@ -66,107 +62,8 @@ struct InputBuffer { u32 fileoffset; }; -struct AtracLoopInfo { - int cuePointID; - int type; - int startSample; - int endSample; - int fraction; - int playCount; -}; - class AudioDecoder; -// This is (mostly) constant info, once a track has been loaded. -struct Track { - // This both does and doesn't belong in Track - it's fixed for an Atrac instance. Oh well. - u32 codecType = 0; - - // Size of the full track being streamed or played. Can be a lot larger than the in-memory buffer in the streaming modes. - u32 fileSize = 0; - - // Not really used for much except queries, this keeps track of the bitrate of the track (kbps). - u32 bitrate = 64; - - // Signifies whether to use a more efficient coding mode with less stereo separation. For our purposes, just metadata, - // not actually used in decoding. - int jointStereo = 0; - - // Number of audio channels in the track. - u16 channels = 2; - - // The size of an encoded frame in bytes. - u16 bytesPerFrame = 0; - - // Byte offset of the first encoded frame in the input buffer. Note: Some samples may be skipped according to firstSampleOffset. - int dataByteOffset = 0; - - // How many samples to skip from the beginning of a track when decoding. - // Actually, the real number is this added to FirstOffsetExtra(codecType). You can call - // FirstSampleOffset2() to get that. - // Some use of these offsets around the code seem to be inconsistent, sometimes the extra is included, - // sometimes not. - int firstSampleOffset = 0; - - // Last sample number. Inclusive. Though, we made it so that in Analyze, it's exclusive in the file. - // Does not take firstSampleOffset into account. - int endSample = -1; - - // NOTE: The below CAN be written. - // Loop configuration. The PSP only supports one loop but we store them all. - std::vector loopinfo; - // The actual used loop offsets. These appear to be raw offsets, not taking FirstSampleOffset2() into account. - int loopStartSample = -1; - int loopEndSample = -1; - - // Input frame size - int BytesPerFrame() const { - return bytesPerFrame; - } - - inline int FirstOffsetExtra() const { - // These first samples are skipped, after first possibly skipping 0-2 full frames, it seems. - return codecType == PSP_MODE_AT_3_PLUS ? 0x170 : 0x45; - } - - // Includes the extra offset. See firstSampleOffset comment above. - int FirstSampleOffsetFull() const { - return FirstOffsetExtra() + firstSampleOffset; - } - - // Output frame size, different between the two supported codecs. - int SamplesPerFrame() const { - return codecType == PSP_MODE_AT_3_PLUS ? ATRAC3PLUS_MAX_SAMPLES : ATRAC3_MAX_SAMPLES; - } - - int Bitrate() const { - int bitrate = (bytesPerFrame * 352800) / 1000; - if (codecType == PSP_MODE_AT_3_PLUS) - bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0; - else - bitrate = (bitrate + 511) >> 10; - return bitrate; - } - - // This appears to be buggy, should probably include FirstOffsetExtra? - // Actually the units don't even make sense here. - int DecodePosBySample(int sample) const { - return (u32)(firstSampleOffset + sample / (int)SamplesPerFrame() * bytesPerFrame); - } - - // This appears to be buggy, should probably include FirstOffsetExtra? - int FileOffsetBySample(int sample) const { - int offsetSample = sample + firstSampleOffset; - int frameOffset = offsetSample / (int)SamplesPerFrame(); - return (u32)(dataByteOffset + bytesPerFrame + frameOffset * bytesPerFrame); - } - - void DebugLog() const; -}; - -int AnalyzeAA3Track(u32 addr, u32 size, u32 filesize, Track *track); -int AnalyzeAtracTrack(u32 addr, u32 size, Track *track); - class AtracBase { public: virtual ~AtracBase(); diff --git a/Core/HLE/AtracCtx2.cpp b/Core/HLE/AtracCtx2.cpp index 03c24d539b..f08e8a9ecb 100644 --- a/Core/HLE/AtracCtx2.cpp +++ b/Core/HLE/AtracCtx2.cpp @@ -503,7 +503,7 @@ u32 Atrac2::AddStreamDataSas(u32 bufPtr, u32 bytesToAdd) { // Sol Trigger is the only game I know that uses this. _dbg_assert_(false); - u8 *dest = Memory::GetPointerWrite(info.buffer); + u8 *dest = Memory::GetPointerWrite(sasBasePtr_ + sasReadOffset_); memcpy(dest, Memory::GetPointer(bufPtr), bytesToAdd); info.buffer += bytesToAdd; info.streamDataByte += bytesToAdd; diff --git a/Core/HLE/sceAtrac.cpp b/Core/HLE/sceAtrac.cpp index 166f770c80..6b465792c2 100644 --- a/Core/HLE/sceAtrac.cpp +++ b/Core/HLE/sceAtrac.cpp @@ -635,9 +635,10 @@ static u32 sceAtracSetHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u32 b } Track track; - int ret = AnalyzeAtracTrack(buffer, readSize, &track); + std::string error; + int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error); if (ret < 0) { - return hleLogError(Log::ME, ret); + return hleLogError(Log::ME, ret, "%s", error.c_str()); } if (track.codecType != atracContextTypes[atracID]) { // TODO: Should this not change the buffer size? @@ -670,9 +671,10 @@ static u32 sceAtracSetData(int atracID, u32 buffer, u32 bufferSize) { } Track track; - int ret = AnalyzeAtracTrack(buffer, bufferSize, &track); + std::string error; + int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error); if (ret < 0) { - return hleLogError(Log::ME, ret); + return hleLogError(Log::ME, ret, "%s", error.c_str()); } if (track.codecType != atracContextTypes[atracID]) { // TODO: Should this not change the buffer size? @@ -697,9 +699,10 @@ static int sceAtracSetDataAndGetID(u32 buffer, int bufferSize) { } Track track; - int ret = AnalyzeAtracTrack(buffer, bufferSize, &track); + std::string error; + int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error); if (ret < 0) { - return hleLogError(Log::ME, ret); + return hleLogError(Log::ME, ret, "%s", error.c_str()); } int atracID = AllocAndRegisterAtrac(track.codecType); @@ -722,9 +725,10 @@ static int sceAtracSetHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 buffer } Track track; - int ret = AnalyzeAtracTrack(buffer, bufferSize, &track); + std::string error; + int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error); if (ret < 0) { - return hleLogError(Log::ME, ret); + return hleLogError(Log::ME, ret, "%s", error.c_str()); } int atracID = AllocAndRegisterAtrac(track.codecType); @@ -841,9 +845,10 @@ static int sceAtracSetMOutHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u } Track track; - int ret = AnalyzeAtracTrack(buffer, bufferSize, &track); + std::string error; + int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error); if (ret < 0) { - return hleLogError(Log::ME, ret); + return hleLogError(Log::ME, ret, "%s", error.c_str()); } ret = atrac->SetData(track, buffer, readSize, bufferSize, 1); @@ -865,9 +870,10 @@ static u32 sceAtracSetMOutData(int atracID, u32 buffer, u32 bufferSize) { } Track track; - int ret = AnalyzeAtracTrack(buffer, bufferSize, &track); + std::string error; + int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error); if (ret < 0) { - return hleLogError(Log::ME, ret); + return hleLogError(Log::ME, ret, "%s", error.c_str()); } ret = atrac->SetData(track, buffer, bufferSize, bufferSize, 1); @@ -883,10 +889,12 @@ static u32 sceAtracSetMOutData(int atracID, u32 buffer, u32 bufferSize) { // See note in above function. static int sceAtracSetMOutDataAndGetID(u32 buffer, u32 bufferSize) { Track track; - int ret = AnalyzeAtracTrack(buffer, bufferSize, &track); + std::string error; + int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error); if (ret < 0) { - return hleLogError(Log::ME, ret); + return hleLogError(Log::ME, ret, "%s", error.c_str()); } + if (track.channels != 1) { return hleReportError(Log::ME, SCE_ERROR_ATRAC_NOT_MONO, "not mono data"); } @@ -910,10 +918,12 @@ static int sceAtracSetMOutHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bu } Track track; - int ret = AnalyzeAtracTrack(buffer, bufferSize, &track); + std::string error; + int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error); if (ret < 0) { - return hleLogError(Log::ME, ret); + return hleLogError(Log::ME, ret, "%s", error.c_str()); } + if (track.channels != 1) { return hleReportError(Log::ME, SCE_ERROR_ATRAC_NOT_MONO, "not mono data"); } @@ -933,9 +943,10 @@ static int sceAtracSetMOutHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bu static int sceAtracSetAA3DataAndGetID(u32 buffer, u32 bufferSize, u32 fileSize, u32 metadataSizeAddr) { Track track; - int ret = AnalyzeAtracTrack(buffer, bufferSize, &track); + std::string error; + int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error); if (ret < 0) { - return hleLogError(Log::ME, ret); + return hleLogError(Log::ME, ret, "%s", error.c_str()); } int atracID = AllocAndRegisterAtrac(track.codecType); @@ -952,6 +963,32 @@ static int sceAtracSetAA3DataAndGetID(u32 buffer, u32 bufferSize, u32 fileSize, return hleDelayResult(hleLogDebug(Log::ME, atracID), "atrac set aa3 data", 100); } +static int sceAtracSetAA3HalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bufferSize, u32 fileSize) { + if (readSize > bufferSize) { + return hleLogError(Log::ME, SCE_ERROR_ATRAC_INCORRECT_READ_SIZE, "read size too large"); + } + + Track track; + std::string error; + int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error); + if (ret < 0) { + return hleLogError(Log::ME, ret, "%s", error.c_str()); + } + + int atracID = AllocAndRegisterAtrac(track.codecType); + if (atracID < 0) { + return hleLogError(Log::ME, atracID, "no free ID"); + } + + ret = atracContexts[atracID]->SetData(track, buffer, readSize, bufferSize, 2); + if (ret < 0) { + UnregisterAndDeleteAtrac(atracID); + return hleLogError(Log::ME, ret); + } + + return hleDelayResult(hleLogDebug(Log::ME, atracID), "atrac set data", 100); +} + // TODO: Should see if these are stored contiguously in memory somewhere, or if there really are // individual allocations being used. static u32 _sceAtracGetContextAddress(int atracID) { @@ -1048,31 +1085,6 @@ static int sceAtracLowLevelDecode(int atracID, u32 sourceAddr, u32 sourceBytesCo return hleDelayResult(hleLogDebug(Log::ME, retval), "low level atrac decode data", atracDecodeDelay); } -static int sceAtracSetAA3HalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bufferSize, u32 fileSize) { - if (readSize > bufferSize) { - return hleLogError(Log::ME, SCE_ERROR_ATRAC_INCORRECT_READ_SIZE, "read size too large"); - } - - Track track; - int ret = AnalyzeAtracTrack(buffer, bufferSize, &track); - if (ret < 0) { - return hleLogError(Log::ME, ret); - } - - int atracID = AllocAndRegisterAtrac(track.codecType); - if (atracID < 0) { - return hleLogError(Log::ME, atracID, "no free ID"); - } - - ret = atracContexts[atracID]->SetData(track, buffer, readSize, bufferSize, 2); - if (ret < 0) { - UnregisterAndDeleteAtrac(atracID); - return hleLogError(Log::ME, ret); - } - - return hleDelayResult(hleLogDebug(Log::ME, atracID), "atrac set data", 100); -} - // These three are the external interface used by sceSas' AT3 integration. // NOTE: There are special rules. diff --git a/Core/HLE/sceAtrac.h b/Core/HLE/sceAtrac.h index e80273b754..1f4fb046dc 100644 --- a/Core/HLE/sceAtrac.h +++ b/Core/HLE/sceAtrac.h @@ -17,7 +17,8 @@ #pragma once -#include "sceAudiocodec.h" +#include "Core/HLE/sceAudiocodec.h" +#include "Core/Util/AtracTrack.h" class PointerWrap; @@ -29,9 +30,6 @@ void __AtracShutdown(); void __AtracNotifyLoadModule(int version, u32 crc, u32 bssAddr, int bssSize); void __AtracNotifyUnloadModule(); -constexpr u32 ATRAC3_MAX_SAMPLES = 0x400; // 1024 -constexpr u32 ATRAC3PLUS_MAX_SAMPLES = 0x800; // 2048 - // The "state" member of SceAtracIdInfo. enum AtracStatus : u8 { ATRAC_STATUS_UNINITIALIZED = 0, // bad state @@ -68,6 +66,9 @@ const char *AtracStatusToString(AtracStatus status); inline bool AtracStatusIsStreaming(AtracStatus status) { return (status & ATRAC_STATUS_STREAMED_MASK) != 0; } +inline bool AtracStatusIsNormal(AtracStatus status) { + return (int)status >= ATRAC_STATUS_ALL_DATA_LOADED && (int)status <= ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER; +} struct SceAtracIdInfo { s32 decodePos; // Sample position in the song that we'll next be decoding from. diff --git a/Core/Util/AtracTrack.cpp b/Core/Util/AtracTrack.cpp new file mode 100644 index 0000000000..e40c6d5555 --- /dev/null +++ b/Core/Util/AtracTrack.cpp @@ -0,0 +1,300 @@ +#include "Common/Log.h" +#include "Common/StringUtils.h" +#include "Core/Util/AtracTrack.h" +#include "Core/HLE/ErrorCodes.h" +#include "Core/MemMap.h" + +const int RIFF_CHUNK_MAGIC = 0x46464952; +const int RIFF_WAVE_MAGIC = 0x45564157; +const int FMT_CHUNK_MAGIC = 0x20746D66; +const int DATA_CHUNK_MAGIC = 0x61746164; +const int SMPL_CHUNK_MAGIC = 0x6C706D73; +const int FACT_CHUNK_MAGIC = 0x74636166; + +static u16 Read16(const u8 *buffer, int offset) { + u16 value; + memcpy(&value, buffer + offset, sizeof(u16)); + return value; +} + +static u32 Read32(const u8 *buffer, int offset) { + u32 value; + memcpy(&value, buffer + offset, sizeof(u32)); + return value; +} + +int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *error) { + // 72 is about the size of the minimum required data to even be valid. + + // TODO: Validate stuff more. + if (Read32(buffer, 0) != RIFF_CHUNK_MAGIC) { + ERROR_LOG(Log::ME, "Couldn't find RIFF header"); + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + + struct RIFFFmtChunk { + u16 fmtTag; + u16 channels; + u32 samplerate; + u32 avgBytesPerSec; + u16 blockAlign; + }; + + u32 offset = 8; + track->firstSampleOffset = 0; + + while (Read32(buffer, offset) != RIFF_WAVE_MAGIC) { + // Get the size preceding the magic. + int chunk = Read32(buffer, offset - 4); + // Round the chunk size up to the nearest 2. + offset += chunk + (chunk & 1); + if (offset + 12 > size) { + *error = StringFromFormat("%d too small for WAVE chunk at offset %d", size, offset); + return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; + } + if (Read32(buffer, offset) != RIFF_CHUNK_MAGIC) { + *error = "RIFF chunk did not contain WAVE"; + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + offset += 8; + } + offset += 4; + + if (offset != 12) { + WARN_LOG(Log::ME, "RIFF chunk at offset: %d", offset); + } + + // RIFF size excluding chunk header. + track->fileSize = Read32(buffer, offset - 8) + 8; + + // Even if the RIFF size is too low, it may simply be incorrect. This works on real firmware. + u32 maxSize = std::max(track->fileSize, size); + + bool bfoundData = false; + u32 dataChunkSize = 0; + int sampleOffsetAdjust = 0; + + while (maxSize >= offset + 8 && !bfoundData) { + int chunkMagic = Read32(buffer, offset); + u32 chunkSize = Read32(buffer, offset + 4); + // Account for odd sized chunks. + if (chunkSize & 1) { + WARN_LOG(Log::ME, "RIFF chunk had uneven size"); + } + chunkSize += (chunkSize & 1); + offset += 8; + if (chunkSize > maxSize - offset) + break; + switch (chunkMagic) { + case FMT_CHUNK_MAGIC: + { + if (track->codecType != 0) { + *error = "AnalyzeTrack: multiple fmt chunks is not valid"; + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + + auto at3fmt = (const RIFFFmtChunk *)(buffer + offset); + if (chunkSize < 32 || (at3fmt->fmtTag == AT3_PLUS_MAGIC && chunkSize < 52)) { + *error = "AnalyzeTrack: fmt definition too small(%d)"; + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + + if (at3fmt->fmtTag == AT3_MAGIC) + track->codecType = PSP_MODE_AT_3; + else if (at3fmt->fmtTag == AT3_PLUS_MAGIC) + track->codecType = PSP_MODE_AT_3_PLUS; + else { + *error = "AnalyzeTrack: invalid fmt magic: %04x"; + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + track->channels = at3fmt->channels; + if (track->channels != 1 && track->channels != 2) { + *error = "AnalyzeTrack: unsupported channel count %d"; + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + if (at3fmt->samplerate != 44100) { + *error = "AnalyzeTrack: unsupported sample rate %d"; + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + track->bitrate = at3fmt->avgBytesPerSec * 8; + track->bytesPerFrame = at3fmt->blockAlign; + if (track->bytesPerFrame == 0) { + *error = "invalid bytes per frame: %d"; + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + + // TODO: There are some format specific bytes here which seem to have fixed values? + // Probably don't need them. + + if (at3fmt->fmtTag == AT3_MAGIC) { + // This is the offset to the jointStereo_ field. + track->jointStereo = Read16(buffer, offset + 24); + // Then there are more fields here. + u16 unknown1_2 = Read16(buffer, offset + 30); + + } + if (chunkSize > 16) { + // Read and format extra bytes as hexadecimal + std::string hex; + DataToHexString(buffer + offset + 16, chunkSize - 16, &hex, false); + DEBUG_LOG(Log::ME, "Additional chunk data (beyond 16 bytes): %s", hex.c_str()); + } + break; + } + case FACT_CHUNK_MAGIC: + { + track->endSample = Read32(buffer, offset); + if (chunkSize >= 8) { + track->firstSampleOffset = Read32(buffer, offset + 4); + } + if (chunkSize >= 12) { + u32 largerOffset = Read32(buffer, offset + 8); + // Works, but "largerOffset"?? + sampleOffsetAdjust = track->firstSampleOffset - largerOffset; + } + break; + } + case SMPL_CHUNK_MAGIC: + { + if (chunkSize < 32) { + *error = StringFromFormat("smpl chunk too small (%d)", chunkSize); + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + int checkNumLoops = Read32(buffer, offset + 28); + if (checkNumLoops != 0 && chunkSize < 36 + 20) { + *error = StringFromFormat("smpl chunk too small for loop (%d, %d)", checkNumLoops, chunkSize); + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + if (checkNumLoops < 0) { + *error = StringFromFormat("bad checkNumLoops (%d)", checkNumLoops); + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + + track->loopinfo.resize(checkNumLoops); + u32 loopinfoOffset = offset + 36; + // The PSP only cares about the first loop start and end, it seems. + // Most likely can skip the rest of this data, but it's not hurting anyone. + for (int i = 0; i < checkNumLoops && 36 + (u32)i < chunkSize; i++, loopinfoOffset += 24) { + track->loopinfo[i].cuePointID = Read32(buffer, loopinfoOffset + 0); + track->loopinfo[i].type = Read32(buffer, loopinfoOffset + 4); + track->loopinfo[i].startSample = Read32(buffer, loopinfoOffset + 8); + track->loopinfo[i].endSample = Read32(buffer, loopinfoOffset + 12); + track->loopinfo[i].fraction = Read32(buffer, loopinfoOffset + 16); + track->loopinfo[i].playCount = Read32(buffer, loopinfoOffset + 20); + if (i == 0 && track->loopinfo[i].startSample >= track->loopinfo[i].endSample) { + *error = "AnalyzeTrack: loop starts after it ends"; + return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS; + } + } + break; + } + case DATA_CHUNK_MAGIC: + { + bfoundData = true; + track->dataByteOffset = offset; + dataChunkSize = chunkSize; + if (track->fileSize < offset + chunkSize) { + WARN_LOG(Log::ME, "Atrac data chunk extends beyond riff chunk"); + track->fileSize = offset + chunkSize; + } + } + break; + } + offset += chunkSize; + } + + if (track->codecType == 0) { + *error = "Could not detect codec"; + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + + if (!bfoundData) { + *error = "AnalyzeTrack: No data chunk found"; + return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; + } + + // set the loopStartSample_ and loopEndSample_ by loopinfo_ + if (track->loopinfo.size() > 0) { + track->loopStartSample = track->loopinfo[0].startSample + track->FirstOffsetExtra() + sampleOffsetAdjust; + track->loopEndSample = track->loopinfo[0].endSample + track->FirstOffsetExtra() + sampleOffsetAdjust; + } else { + track->loopStartSample = -1; + track->loopEndSample = -1; + } + + // if there is no correct endsample, try to guess it + if (track->endSample <= 0 && track->bytesPerFrame != 0) { + track->endSample = (dataChunkSize / track->bytesPerFrame) * track->SamplesPerFrame(); + track->endSample -= track->FirstSampleOffsetFull(); + } + track->endSample -= 1; + + if (track->loopEndSample != -1 && track->loopEndSample > track->endSample + track->FirstSampleOffsetFull()) { + *error = "AnalyzeTrack: loop after end of data"; + return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS; + } + + return 0; +} + +int AnalyzeAA3Track(const u8 *buffer, u32 size, u32 fileSize, Track *track, std::string *error) { + if (size < 10) { + return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL; + } + // TODO: Make sure this validation is correct, more testing. + + if (buffer[0] != 'e' || buffer[1] != 'a' || buffer[2] != '3') { + return SCE_ERROR_ATRAC_AA3_INVALID_DATA; + } + + // It starts with an id3 header (replaced with ea3.) This is the size. + u32 tagSize = buffer[9] | (buffer[8] << 7) | (buffer[7] << 14) | (buffer[6] << 21); + if (size < tagSize + 36) { + return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL; + } + + // EA3 header starts at id3 header (10) + tagSize. + buffer = buffer + 10 + tagSize; + if (buffer[0] != 'E' || buffer[1] != 'A' || buffer[2] != '3') { + ERROR_LOG(Log::ME, "AnalyzeAA3Track: Invalid EA3 magic bytes"); + return SCE_ERROR_ATRAC_AA3_INVALID_DATA; + } + + track->fileSize = fileSize; + + // Based on FFmpeg's code. + u32 codecParams = buffer[33] | (buffer[34] << 8) | (buffer[35] << 16); + const u32 at3SampleRates[8] = { 32000, 44100, 48000, 88200, 96000, 0 }; + + switch (buffer[32]) { + case 0: + track->codecType = PSP_MODE_AT_3; + track->bytesPerFrame = (codecParams & 0x03FF) * 8; + track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 1024; + track->channels = 2; + track->jointStereo = (codecParams >> 17) & 1; + break; + case 1: + track->codecType = PSP_MODE_AT_3_PLUS; + track->bytesPerFrame = ((codecParams & 0x03FF) * 8) + 8; + track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 2048; + track->channels = (codecParams >> 10) & 7; + break; + case 3: + case 4: + case 5: + ERROR_LOG(Log::ME, "AnalyzeAA3Track: unsupported codec type %d", buffer[32]); + return SCE_ERROR_ATRAC_AA3_INVALID_DATA; + default: + ERROR_LOG(Log::ME, "AnalyzeAA3Track: invalid codec type %d", buffer[32]); + return SCE_ERROR_ATRAC_AA3_INVALID_DATA; + } + + track->dataByteOffset = 10 + tagSize + 96; + track->firstSampleOffset = 0; + if (track->endSample < 0 && track->bytesPerFrame != 0) { + track->endSample = ((track->fileSize - track->dataByteOffset) / track->bytesPerFrame) * track->SamplesPerFrame(); + } + track->endSample -= 1; + return 0; +} diff --git a/Core/Util/AtracTrack.h b/Core/Util/AtracTrack.h new file mode 100644 index 0000000000..9c83ec58e2 --- /dev/null +++ b/Core/Util/AtracTrack.h @@ -0,0 +1,114 @@ +#pragma once + +#include "Common/CommonTypes.h" + +#include +#include + +// Atrac file parsing. +#define AT3_MAGIC 0x0270 +#define AT3_PLUS_MAGIC 0xFFFE +#define PSP_MODE_AT_3_PLUS 0x00001000 +#define PSP_MODE_AT_3 0x00001001 + +constexpr u32 ATRAC3_MAX_SAMPLES = 0x400; // 1024 +constexpr u32 ATRAC3PLUS_MAX_SAMPLES = 0x800; // 2048 + +struct AtracLoopInfo { + int cuePointID; + int type; + int startSample; + int endSample; + int fraction; + int playCount; +}; + +// This is (mostly) constant info, once a track has been loaded. +struct Track { + // This both does and doesn't belong in Track - it's fixed for an Atrac instance. Oh well. + u32 codecType = 0; + + // Size of the full track being streamed or played. Can be a lot larger than the in-memory buffer in the streaming modes. + u32 fileSize = 0; + + // Not really used for much except queries, this keeps track of the bitrate of the track (kbps). + u32 bitrate = 64; + + // Signifies whether to use a more efficient coding mode with less stereo separation. For our purposes, just metadata, + // not actually used in decoding. + int jointStereo = 0; + + // Number of audio channels in the track. + u16 channels = 2; + + // The size of an encoded frame in bytes. + u16 bytesPerFrame = 0; + + // Byte offset of the first encoded frame in the input buffer. Note: Some samples may be skipped according to firstSampleOffset. + int dataByteOffset = 0; + + // How many samples to skip from the beginning of a track when decoding. + // Actually, the real number is this added to FirstOffsetExtra(codecType). You can call + // FirstSampleOffset2() to get that. + // Some use of these offsets around the code seem to be inconsistent, sometimes the extra is included, + // sometimes not. + int firstSampleOffset = 0; + + // Last sample number. Inclusive. Though, we made it so that in Analyze, it's exclusive in the file. + // Does not take firstSampleOffset into account. + int endSample = -1; + + // NOTE: The below CAN be written. + // Loop configuration. The PSP only supports one loop but we store them all. + std::vector loopinfo; + // The actual used loop offsets. These appear to be raw offsets, not taking FirstSampleOffset2() into account. + int loopStartSample = -1; + int loopEndSample = -1; + + // Input frame size + int BytesPerFrame() const { + return bytesPerFrame; + } + + inline int FirstOffsetExtra() const { + // These first samples are skipped, after first possibly skipping 0-2 full frames, it seems. + return codecType == PSP_MODE_AT_3_PLUS ? 0x170 : 0x45; + } + + // Includes the extra offset. See firstSampleOffset comment above. + int FirstSampleOffsetFull() const { + return FirstOffsetExtra() + firstSampleOffset; + } + + // Output frame size, different between the two supported codecs. + int SamplesPerFrame() const { + return codecType == PSP_MODE_AT_3_PLUS ? ATRAC3PLUS_MAX_SAMPLES : ATRAC3_MAX_SAMPLES; + } + + int Bitrate() const { + int bitrate = (bytesPerFrame * 352800) / 1000; + if (codecType == PSP_MODE_AT_3_PLUS) + bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0; + else + bitrate = (bitrate + 511) >> 10; + return bitrate; + } + + // This appears to be buggy, should probably include FirstOffsetExtra? + // Actually the units don't even make sense here. + int DecodePosBySample(int sample) const { + return (u32)(firstSampleOffset + sample / (int)SamplesPerFrame() * bytesPerFrame); + } + + // This appears to be buggy, should probably include FirstOffsetExtra? + int FileOffsetBySample(int sample) const { + int offsetSample = sample + firstSampleOffset; + int frameOffset = offsetSample / (int)SamplesPerFrame(); + return (u32)(dataByteOffset + bytesPerFrame + frameOffset * bytesPerFrame); + } + + void DebugLog() const; +}; + +int AnalyzeAA3Track(const u8 *buffer, u32 size, u32 filesize, Track *track, std::string *error); +int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *error); diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index 8aef6b6bf0..f572587a7c 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -326,6 +326,9 @@ bool MainUI::HandleCustomEvent(QEvent *e) { case BrowseFileType::ZIP: filter = "ZIP files (*.zip)"; break; + case BrowseFileType::ATRAC3: + filter = "AT3 files (*.at3)"; + break; case BrowseFileType::ANY: break; } diff --git a/UI/DarwinFileSystemServices.mm b/UI/DarwinFileSystemServices.mm index d3e7ff7c2f..3458eb2149 100644 --- a/UI/DarwinFileSystemServices.mm +++ b/UI/DarwinFileSystemServices.mm @@ -90,6 +90,9 @@ void DarwinFileSystemServices::presentDirectoryPanel( case BrowseFileType::SOUND_EFFECT: [panel setAllowedFileTypes:[NSArray arrayWithObject:@"wav"]]; break; + case BrowseFileType::ATRAC3: + [panel setAllowedFileTypes:[NSArray arrayWithObject:@"at3"]]; + break; default: break; } diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 7d6d796549..b7a31b2c58 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -33,6 +33,7 @@ #include "Core/HLE/sceNetAdhocMatching.h" #include "Common/System/Request.h" +#include "Core/Util/AtracTrack.h" #include "Core/HLE/sceAtrac.h" #include "Core/HLE/sceAudio.h" #include "Core/HLE/sceAudiocodec.h" @@ -1000,7 +1001,7 @@ void DrawAudioDecodersView(ImConfig &cfg, ImControl &control) { ImGui::TableNextColumn(); ImGui::Text("in:%d out:%d", ctx->Channels(), ctx->GetOutputChannels()); ImGui::TableNextColumn(); - if (ctx->BufferState() != ATRAC_STATUS_LOW_LEVEL) { + if (AtracStatusIsNormal(ctx->BufferState())) { int pos; ctx->GetNextDecodePosition(&pos); ImGui::Text("%d", pos); @@ -1008,7 +1009,7 @@ void DrawAudioDecodersView(ImConfig &cfg, ImControl &control) { ImGui::TextUnformatted("N/A"); } ImGui::TableNextColumn(); - if (ctx->BufferState() <= ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER) { + if (AtracStatusIsNormal(ctx->BufferState())) { ImGui::Text("%d", ctx->RemainingFrames()); } else { ImGui::TextUnformatted("N/A"); @@ -1479,6 +1480,44 @@ static void DrawModules(const MIPSDebugInterface *debug, ImConfig &cfg, ImContro ImGui::End(); } +void ImAtracToolWindow::Draw(ImConfig &cfg) { + if (!ImGui::Begin("Atrac Tool", &cfg.atracToolOpen) || !g_symbolMap) { + ImGui::End(); + return; + } + + ImGui::InputText("File", atracPath_, sizeof(atracPath_)); + ImGui::SameLine(); + if (ImGui::Button("Choose...")) { + System_BrowseForFile(cfg.requesterToken, "Choose AT3 file", BrowseFileType::ATRAC3, [&](const std::string &filename, int) { + truncate_cpy(atracPath_, filename); + }, nullptr); + } + + if (strlen(atracPath_) > 0) { + if (ImGui::Button("Load")) { + track_.reset(new Track()); + std::string data; + if (File::ReadBinaryFileToString(Path(atracPath_), &data)) { + AnalyzeAtracTrack((const u8 *)data.data(), (u32)data.size(), track_.get(), &error_); + } + } + } + + if (track_.get() != 0) { + ImGui::Text("Codec: %s", track_->codecType != PSP_CODEC_AT3 ? "at3+" : "at3"); + ImGui::Text("Bitrate: %d kbps Channels: %d", track_->Bitrate(), track_->channels); + ImGui::Text("Frame size in bytes: %d Output frame in samples: %d", track_->BytesPerFrame(), track_->SamplesPerFrame()); + ImGui::Text("First valid sample: %08x", track_->FirstSampleOffsetFull()); + } + + if (!error_.empty()) { + ImGui::TextUnformatted(error_.c_str()); + } + + ImGui::End(); +} + void DrawHLEModules(ImConfig &config) { if (!ImGui::Begin("HLE Modules", &config.hleModulesOpen)) { ImGui::End(); @@ -1679,6 +1718,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu ImGui::MenuItem("Debug stats", nullptr, &cfg_.debugStatsOpen); ImGui::MenuItem("Struct viewer", nullptr, &cfg_.structViewerOpen); ImGui::MenuItem("Log channels", nullptr, &cfg_.logConfigOpen); + ImGui::MenuItem("Atrac Tool", nullptr, &cfg_.atracToolOpen); ImGui::EndMenu(); } if (ImGui::BeginMenu("Misc")) { @@ -1761,6 +1801,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu DrawHLEModules(cfg_); } + if (cfg_.atracToolOpen) { + atracToolWindow_.Draw(cfg_); + } + if (cfg_.framebuffersOpen) { DrawFramebuffersWindow(cfg_, gpuDebug->GetFramebufferManagerCommon()); } diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 54bd997cb6..aa3b01dadb 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -150,6 +150,7 @@ struct ImConfig { bool sasAudioOpen; bool logConfigOpen; bool utilityModulesOpen; + bool atracToolOpen; bool memViewOpen[4]; // HLE explorer settings @@ -176,6 +177,7 @@ struct ImConfig { bool sasShowAllVoices = false; + // We use a separate ini file from the main PPSSPP config. void LoadConfig(const Path &iniFile); void SaveConfig(const Path &iniFile); @@ -183,6 +185,16 @@ struct ImConfig { void SyncConfig(IniFile *ini, bool save); }; +struct Track; +class ImAtracToolWindow { +public: + void Draw(ImConfig &cfg); + + char atracPath_[1024]{}; + std::unique_ptr track_; + std::string error_; +}; + enum class ImCmd { NONE = 0, TRIGGER_FIND_POPUP, @@ -233,6 +245,7 @@ private: ImStructViewer structViewer_; ImGePixelViewerWindow pixelViewer_; ImMemDumpWindow memDumpWindow_; + ImAtracToolWindow atracToolWindow_; ImSnapshotState newSnapshot_; ImSnapshotState snapshot_; diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 197d4dbe3a..c88ae1961d 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -333,6 +333,7 @@ + @@ -632,6 +633,7 @@ + @@ -1040,4 +1042,4 @@ - + \ No newline at end of file diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 645ab2e4f6..8426b68001 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -1235,6 +1235,9 @@ HLE + + Util + @@ -1954,10 +1957,13 @@ HLE + + Util + Ext\gason - + \ No newline at end of file diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 93712f5fad..223a1e41a5 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -517,7 +517,7 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string supportedExtensions = { ".zip" }; break; case BrowseFileType::SYMBOL_MAP: - supportedExtensions = { ".map" }; + supportedExtensions = { ".ppmap" }; break; case BrowseFileType::DB: supportedExtensions = { ".db" }; @@ -525,6 +525,9 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string case BrowseFileType::SOUND_EFFECT: supportedExtensions = { ".wav", ".mp3" }; break; + case BrowseFileType::ATRAC3: + supportedExtensions = { ".at3" }; + break; case BrowseFileType::ANY: // 'ChooseFile' will added '*' by default when there are no extensions assigned break; diff --git a/Windows/main.cpp b/Windows/main.cpp index 0efb643d96..5b38d02c75 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -537,6 +537,8 @@ static std::wstring MakeWindowsFilter(BrowseFileType type) { return FinalizeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||"); case BrowseFileType::SYMBOL_MAP: return FinalizeFilter(L"Symbol map files (*.ppmap)|*.ppmap|All files (*.*)|*.*||"); + case BrowseFileType::ATRAC3: + return FinalizeFilter(L"ATRAC3/3+ files (*.at3)|*.at3|All files (*.*)|*.*||"); case BrowseFileType::ANY: return FinalizeFilter(L"All files (*.*)|*.*||"); default: diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 95b758d9a0..405e2345eb 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -753,6 +753,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/MIPS/JitCommon/JitCommon.cpp \ $(SRC)/Core/MIPS/JitCommon/JitBlockCache.cpp \ $(SRC)/Core/MIPS/JitCommon/JitState.cpp \ + $(SRC)/Core/Util/AtracTrack.cpp \ $(SRC)/Core/Util/AudioFormat.cpp \ $(SRC)/Core/Util/MemStick.cpp \ $(SRC)/Core/Util/PortManager.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 32f7f08d35..4c4d836116 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -690,8 +690,8 @@ SOURCES_CXX += \ $(COREDIR)/Font/PGF.cpp \ $(COREDIR)/HLE/HLE.cpp \ $(COREDIR)/HLE/KUBridge.cpp \ - $(COREDIR)/HLE/NetInetConstants.cpp \ - $(COREDIR)/HLE/SocketManager.cpp \ + $(COREDIR)/HLE/NetInetConstants.cpp \ + $(COREDIR)/HLE/SocketManager.cpp \ $(COREDIR)/HLE/Plugins.cpp \ $(COREDIR)/HLE/sceSha256.cpp \ $(COREDIR)/HLE/sceSircs.cpp \ @@ -826,6 +826,7 @@ SOURCES_CXX += \ $(COREDIR)/Screenshot.cpp \ $(COREDIR)/System.cpp \ $(COREDIR)/ThreadPools.cpp \ + $(COREDIR)/Util/AtracTrack.cpp \ $(COREDIR)/Util/BlockAllocator.cpp \ $(COREDIR)/Util/MemStick.cpp \ $(COREDIR)/Util/PPGeDraw.cpp \