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
@@ -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 \