diff --git a/CMakeLists.txt b/CMakeLists.txt
index ed768462b3..4cb1106a47 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2186,6 +2186,7 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/HLE/__sceAudio.h
Core/HLE/sceAdler.cpp
Core/HLE/sceAdler.h
+ Core/HLE/AtracBase.h
Core/HLE/AtracCtx.cpp
Core/HLE/AtracCtx.h
Core/HLE/AtracCtx2.cpp
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index 6322bcf448..bafe4e353c 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -1190,6 +1190,7 @@
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index 3c50aa21c9..e9d8adab35 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -2190,6 +2190,9 @@
FileLoaders
+
+ HLE\Libraries
+
diff --git a/Core/HLE/AtracBase.h b/Core/HLE/AtracBase.h
new file mode 100644
index 0000000000..c6b6c96619
--- /dev/null
+++ b/Core/HLE/AtracBase.h
@@ -0,0 +1,248 @@
+#pragma once
+
+#include "Common/CommonTypes.h"
+#include "Common/Swap.h"
+
+#include "Core/MemMap.h"
+#include "Core/HLE/sceAudiocodec.h"
+
+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
+
+ ATRAC_STATUS_NO_DATA = 1,
+
+ // The entire file is loaded into memory, no further file access needed.
+ ATRAC_STATUS_ALL_DATA_LOADED = 2,
+
+ // The buffer is sized to fit the entire file, but it's only partially loaded, so you can start playback before loading the whole file.
+ ATRAC_STATUS_HALFWAY_BUFFER = 3,
+
+ // In these ones, the buffer is smaller than the file, and data is streamed into it as needed for playback.
+ // These are the most complex modes, both to implement and use.
+ ATRAC_STATUS_STREAMED_WITHOUT_LOOP = 4,
+ ATRAC_STATUS_STREAMED_LOOP_FROM_END = 5,
+ // This means there's additional audio after the loop.
+ // i.e. ~~before loop~~ [ ~~this part loops~~ ] ~~after loop~~
+ // The "fork in the road" means a second buffer is needed for the second path.
+ ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER = 6,
+
+ // In this mode, the only API to call is sceAtracLowLevelDecode, which decodes a stream packet by packet without any other metadata.
+ ATRAC_STATUS_LOW_LEVEL = 8,
+
+ // This mode is for using an Atrac context as the audio source for an sceSas channel. Not used a lot (Sol Trigger).
+ ATRAC_STATUS_FOR_SCESAS = 16,
+
+ // Bitwise-and the status with this to check for any of the streaming modes in a single test.
+ ATRAC_STATUS_STREAMED_MASK = 4,
+};
+
+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.
+ s32 endSample; // Last sample index of the track.
+ s32 loopStart; // Start of the loop (sample index)
+ s32 loopEnd; // End of the loop (sample index)
+ s32 firstValidSample; // Seems to be the number of skipped samples at the start. After SetID, decodePos will match this. Was previously misnamed 'samplesPerChan'.
+ u8 numSkipFrames; // This is 1 for a single frame when a loop is triggered, otherwise seems to stay at 0. Likely mis-named.
+ AtracStatus state; // State enum, see AtracStatus.
+ u8 curBuffer; // Current buffer (1 == second, 2 == done?) Previously unk
+ u8 numChan; // Number of audio channels, usually 2 but 1 is possible.
+ u16 sampleSize; // Size in bytes of an encoded audio frame.
+ u16 codec; // Codec. 0x1000 is Atrac3+, 0x1001 is Atrac3. See the PSP_CODEC_ enum (only these two are supported).
+ s32 dataOff; // File offset in bytes where the Atrac3+ frames start appearing. The first dummy packet starts here.
+ s32 curFileOff; // File offset in bytes corresponding to the start of next packet that will be *decoded* (on the next call to sceAtracDecodeData).
+ s32 fileDataEnd; // File size in bytes.
+ s32 loopNum; // Current loop counter. If 0, will not loop. -1 loops for ever, positive numbers get decremented on the loop end. So to play a song 3 times and then end, set this to 2.
+ s32 streamDataByte; // Number of bytes of queued/buffered/uploaded data. In full and half-way modes, this isn't decremented as you decode.
+ s32 streamOff; // Streaming modes only: The byte offset inside the RAM buffer where sceAtracDecodeData will read from next. ONLY points to even packet boundaries.
+ s32 secondStreamOff; // A kind of stream position in the secondary buffer.
+ u32 buffer; // Address in RAM of the main buffer.
+ u32 secondBuffer; // Address in RAM of the second buffer, or 0 if not used.
+ u32 bufferByte; // Size in bytes of the main buffer.
+ u32 secondBufferByte; // Size in bytes of the second buffer.
+ // Offset 72 here.
+ // make sure the size is 128
+ u32 unk[14];
+
+ // Simple helpers. Similar ones are on track_, but we shouldn't need track_ anymore when playing back.
+
+ int SamplesPerFrame() const {
+ return codec == 0x1000 ? ATRAC3PLUS_MAX_SAMPLES : ATRAC3_MAX_SAMPLES;
+ }
+ int SamplesFrameMask() const {
+ return SamplesPerFrame() - 1;
+ }
+ int SkipSamples() const {
+ // These first samples are skipped, after first possibly skipping 0-2 full frames, it seems.
+ return codec == 0x1000 ? 0x170 : 0x45;
+ }
+ int BitRate() const {
+ int bitrate = (sampleSize * 352800) / 1000;
+ if (codec == PSP_CODEC_AT3PLUS) {
+ bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0;
+ } else {
+ bitrate = (bitrate + 511) >> 10;
+ }
+ return bitrate;
+ }
+};
+
+// One of these structs is allocated for each Atrac context.
+// The raw codec state is stored in 'codec'.
+// The internal playback state is stored in 'info', and that is used for all state keeping in the Atrac2 implementation,
+// imitating what happens on hardware as closely as possible.
+struct SceAtracContext {
+ // size 128
+ SceAudiocodecCodec codec;
+ // size 128
+ SceAtracIdInfo info;
+};
+
+struct Atrac3LowLevelParams {
+ int encodedChannels;
+ int outputChannels;
+ int bytesPerFrame;
+};
+
+struct AtracSingleResetBufferInfo {
+ u32_le writePosPtr;
+ u32_le writableBytes;
+ u32_le minWriteBytes;
+ u32_le filePos;
+};
+
+struct AtracResetBufferInfo {
+ AtracSingleResetBufferInfo first;
+ AtracSingleResetBufferInfo second;
+};
+
+struct AtracSasStreamState {
+ u32 bufPtr[2]{};
+ u32 bufSize[2]{};
+ int streamOffset = 0;
+ int fileOffset = 0;
+ int curBuffer = 0;
+ bool isStreaming = false;
+
+ int CurPos() const {
+ int retval = fileOffset - bufSize[curBuffer] + streamOffset;
+ _dbg_assert_(retval >= 0);
+ return retval;
+ }
+};
+
+const int PSP_ATRAC_ALLDATA_IS_ON_MEMORY = -1;
+const int PSP_ATRAC_NONLOOP_STREAM_DATA_IS_ON_MEMORY = -2;
+const int PSP_ATRAC_LOOP_STREAM_DATA_IS_ON_MEMORY = -3;
+
+// This is not a PSP-native struct.
+// But, it's stored in its entirety in savestates, which makes it awkward to change it.
+// This is used for both first_ and second_, but the latter doesn't use all the fields.
+struct InputBuffer {
+ // Address of the buffer.
+ u32 addr;
+ // Size of data read so far into dataBuf_ (to be removed.)
+ u32 size;
+ // Offset into addr at which new data is added.
+ u32 offset;
+ // Last writableBytes number (to be removed.)
+ u32 writableBytes;
+ // Unused, always 0.
+ u32 neededBytes;
+ // Total size of the entire file data.
+ u32 _filesize_dontuse;
+ // Offset into the file at which new data is read.
+ u32 fileoffset;
+};
+
+class AudioDecoder;
+class PointerWrap;
+struct Track;
+
+class AtracBase {
+public:
+ virtual ~AtracBase();
+
+ virtual void DoState(PointerWrap &p) = 0;
+
+ // TODO: Find a way to get rid of this from the base class.
+ virtual void UpdateContextFromPSPMem() = 0;
+
+ virtual int Channels() const = 0;
+
+ int GetOutputChannels() const {
+ return outputChannels_;
+ }
+ void SetOutputChannels(int channels) {
+ // Only used for sceSas audio. To be refactored away in the future.
+ outputChannels_ = channels;
+ }
+
+ virtual u32 GetInternalCodecError() const { return 0; }
+
+ PSPPointer context_{};
+
+ virtual AtracStatus BufferState() const = 0;
+
+ virtual int SetLoopNum(int loopNum) = 0;
+ virtual int LoopNum() const = 0;
+ virtual int LoopStatus() const = 0;
+
+ virtual int CodecType() const = 0;
+
+ AudioDecoder *Decoder() const {
+ return decoder_;
+ }
+
+ void CreateDecoder(int codecType, int bytesPerFrame, int channels);
+
+ virtual void NotifyGetContextAddress() = 0;
+
+ virtual int GetNextDecodePosition(int *pos) const = 0;
+ virtual int RemainingFrames() const = 0;
+ virtual bool HasSecondBuffer() const = 0;
+ virtual int Bitrate() const = 0;
+ virtual int BytesPerFrame() const = 0;
+ virtual int SamplesPerFrame() const = 0;
+
+ virtual void GetStreamDataInfo(u32 *writePtr, u32 *writableBytes, u32 *readOffset) = 0; // This should be const, but the legacy impl stops it (it's wrong).
+ virtual int AddStreamData(u32 bytesToAdd) = 0;
+ virtual int ResetPlayPosition(int sample, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf, bool *delay) = 0;
+ virtual int GetBufferInfoForResetting(AtracResetBufferInfo *bufferInfo, int sample, bool *delay) = 0; // NOTE: Not const! This can cause SkipFrames!
+ virtual int SetData(const Track &track, u32 buffer, u32 readSize, u32 bufferSize, int outputChannels) = 0;
+
+ virtual int GetSecondBufferInfo(u32 *fileOffset, u32 *desiredSize) const = 0;
+ virtual int SetSecondBuffer(u32 secondBuffer, u32 secondBufferSize) = 0;
+ virtual u32 DecodeData(u8 *outbuf, u32 outbufPtr, int *SamplesNum, int *finish, int *remains) = 0;
+ virtual int DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData, int *bytesWritten) = 0;
+
+ virtual u32 GetNextSamples() = 0; // This should be const, but the legacy impl stops it (it's wrong).
+ virtual void InitLowLevel(const Atrac3LowLevelParams ¶ms, int codecType) = 0;
+
+ virtual void CheckForSas() = 0;
+ virtual int EnqueueForSas(u32 address, u32 ptr) = 0;
+ virtual void DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) = 0;
+ virtual const AtracSasStreamState *StreamStateForSas() const { return nullptr; }
+
+ virtual int GetSoundSample(int *endSample, int *loopStartSample, int *loopEndSample) const = 0;
+
+ virtual int GetContextVersion() const = 0;
+
+protected:
+ u16 outputChannels_ = 2;
+
+ // TODO: Save the internal state of this, now technically possible.
+ AudioDecoder *decoder_ = nullptr;
+};
diff --git a/Core/HLE/AtracCtx.cpp b/Core/HLE/AtracCtx.cpp
index 30673e517d..9f9b84271f 100644
--- a/Core/HLE/AtracCtx.cpp
+++ b/Core/HLE/AtracCtx.cpp
@@ -28,8 +28,7 @@
#include "Core/HLE/AtracCtx.h"
#include "Core/HW/Atrac3Standalone.h"
#include "Core/HLE/sceKernelMemory.h"
-#include
-#include
+
const size_t overAllocBytes = 16384;
diff --git a/Core/HLE/AtracCtx.h b/Core/HLE/AtracCtx.h
index 75fa627af1..17dea45e38 100644
--- a/Core/HLE/AtracCtx.h
+++ b/Core/HLE/AtracCtx.h
@@ -23,136 +23,8 @@
#include "Common/Swap.h"
#include "Core/MemMap.h"
-#include "Core/HLE/sceAtrac.h"
-
-struct AtracSingleResetBufferInfo {
- u32_le writePosPtr;
- u32_le writableBytes;
- u32_le minWriteBytes;
- u32_le filePos;
-};
-
-struct AtracResetBufferInfo {
- AtracSingleResetBufferInfo first;
- AtracSingleResetBufferInfo second;
-};
-
-struct AtracSasStreamState {
- u32 bufPtr[2]{};
- u32 bufSize[2]{};
- int streamOffset = 0;
- int fileOffset = 0;
- int curBuffer = 0;
- bool isStreaming = false;
-
- int CurPos() const {
- int retval = fileOffset - bufSize[curBuffer] + streamOffset;
- _dbg_assert_(retval >= 0);
- return retval;
- }
-};
-
-const int PSP_ATRAC_ALLDATA_IS_ON_MEMORY = -1;
-const int PSP_ATRAC_NONLOOP_STREAM_DATA_IS_ON_MEMORY = -2;
-const int PSP_ATRAC_LOOP_STREAM_DATA_IS_ON_MEMORY = -3;
-
-// This is not a PSP-native struct.
-// But, it's stored in its entirety in savestates, which makes it awkward to change it.
-// This is used for both first_ and second_, but the latter doesn't use all the fields.
-struct InputBuffer {
- // Address of the buffer.
- u32 addr;
- // Size of data read so far into dataBuf_ (to be removed.)
- u32 size;
- // Offset into addr at which new data is added.
- u32 offset;
- // Last writableBytes number (to be removed.)
- u32 writableBytes;
- // Unused, always 0.
- u32 neededBytes;
- // Total size of the entire file data.
- u32 _filesize_dontuse;
- // Offset into the file at which new data is read.
- u32 fileoffset;
-};
-
-class AudioDecoder;
-
-class AtracBase {
-public:
- virtual ~AtracBase();
-
- virtual void DoState(PointerWrap &p) = 0;
-
- // TODO: Find a way to get rid of this from the base class.
- virtual void UpdateContextFromPSPMem() = 0;
-
- virtual int Channels() const = 0;
-
- int GetOutputChannels() const {
- return outputChannels_;
- }
- void SetOutputChannels(int channels) {
- // Only used for sceSas audio. To be refactored away in the future.
- outputChannels_ = channels;
- }
-
- virtual u32 GetInternalCodecError() const { return 0; }
-
- PSPPointer context_{};
-
- virtual AtracStatus BufferState() const = 0;
-
- virtual int SetLoopNum(int loopNum) = 0;
- virtual int LoopNum() const = 0;
- virtual int LoopStatus() const = 0;
-
- virtual int CodecType() const = 0;
-
- AudioDecoder *Decoder() const {
- return decoder_;
- }
-
- void CreateDecoder(int codecType, int bytesPerFrame, int channels);
-
- virtual void NotifyGetContextAddress() = 0;
-
- virtual int GetNextDecodePosition(int *pos) const = 0;
- virtual int RemainingFrames() const = 0;
- virtual bool HasSecondBuffer() const = 0;
- virtual int Bitrate() const = 0;
- virtual int BytesPerFrame() const = 0;
- virtual int SamplesPerFrame() const = 0;
-
- virtual void GetStreamDataInfo(u32 *writePtr, u32 *writableBytes, u32 *readOffset) = 0; // This should be const, but the legacy impl stops it (it's wrong).
- virtual int AddStreamData(u32 bytesToAdd) = 0;
- virtual int ResetPlayPosition(int sample, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf, bool *delay) = 0;
- virtual int GetBufferInfoForResetting(AtracResetBufferInfo *bufferInfo, int sample, bool *delay) = 0; // NOTE: Not const! This can cause SkipFrames!
- virtual int SetData(const Track &track, u32 buffer, u32 readSize, u32 bufferSize, int outputChannels) = 0;
-
- virtual int GetSecondBufferInfo(u32 *fileOffset, u32 *desiredSize) const = 0;
- virtual int SetSecondBuffer(u32 secondBuffer, u32 secondBufferSize) = 0;
- virtual u32 DecodeData(u8 *outbuf, u32 outbufPtr, int *SamplesNum, int *finish, int *remains) = 0;
- virtual int DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData, int *bytesWritten) = 0;
-
- virtual u32 GetNextSamples() = 0; // This should be const, but the legacy impl stops it (it's wrong).
- virtual void InitLowLevel(const Atrac3LowLevelParams ¶ms, int codecType) = 0;
-
- virtual void CheckForSas() = 0;
- virtual int EnqueueForSas(u32 address, u32 ptr) = 0;
- virtual void DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) = 0;
- virtual const AtracSasStreamState *StreamStateForSas() const { return nullptr; }
-
- virtual int GetSoundSample(int *endSample, int *loopStartSample, int *loopEndSample) const = 0;
-
- virtual int GetContextVersion() const = 0;
-
-protected:
- u16 outputChannels_ = 2;
-
- // TODO: Save the internal state of this, now technically possible.
- AudioDecoder *decoder_ = nullptr;
-};
+#include "Core/HLE/AtracBase.h"
+#include "Core/Util/AtracTrack.h"
class Atrac : public AtracBase {
public:
diff --git a/Core/HLE/AtracCtx2.cpp b/Core/HLE/AtracCtx2.cpp
index 8fd967ac93..009e124860 100644
--- a/Core/HLE/AtracCtx2.cpp
+++ b/Core/HLE/AtracCtx2.cpp
@@ -9,6 +9,7 @@
#include "Core/HLE/FunctionWrappers.h"
#include "Core/System.h"
#include "Core/HLE/AtracCtx2.h"
+#include "Core/Util/AtracTrack.h"
#include "Core/HW/Atrac3Standalone.h"
#include "Core/Config.h"
#include "Core/MemMap.h"
@@ -28,7 +29,19 @@
// cmd1> C:\dev\ppsspp\pspautotests\tests\audio\atrac>copy /Y ..\..\..\__testoutput.txt stream.expected
// Then run the test, see above.
-// TODO: Add an AT3 dumping facility (and/or replacement?). Although the old implementation could do it more easily...
+struct AT3BitrateMeta {
+ u16 sampleSize;
+ u8 dataByte;
+ u8 jointStereo; // I think?
+};
+
+static const AT3BitrateMeta g_at3BitrateMeta[5] = {
+ {0x180, 0x04, 0},
+ {0x130, 0x06, 0},
+ {0x0C0, 0x0B, 1},
+ {0x0C0, 0x0E, 0},
+ {0x098, 0x0F, 0},
+};
// Needs to support negative numbers, and to handle non-powers-of-two.
static int RoundDownToMultiple(int size, int grain) {
@@ -130,6 +143,118 @@ static int ComputeRemainFrameLooped(const SceAtracIdInfo &info) {
return remainFrames;
}
+static void InitLengthAndLoop(SceAtracIdInfo *ctx, int endSample, int waveDataSize, int firstSampleOffset, int loopBegin, int loopEnd) {
+ const int off = ctx->codec == 0x1001 ? 0x45 : 0x170;
+ const int blockShift = 0x100b - ctx->codec;
+ const int firstValidSample = firstSampleOffset + off;
+ int numSamplesInFile;
+ if (endSample == 0) {
+ numSamplesInFile = waveDataSize / ctx->sampleSize << (blockShift & 0x1f);
+ } else {
+ numSamplesInFile = endSample + firstValidSample;
+ }
+ ctx->decodePos = firstValidSample;
+ ctx->loopNum = 0;
+ ctx->endSample = numSamplesInFile - 1;
+ ctx->numSkipFrames = (char)(firstValidSample >> (blockShift & 0x1f));
+ if (-1 < loopBegin) {
+ ctx->loopEnd = loopEnd + off;
+ ctx->loopStart = loopBegin + off;
+ return;
+ }
+ ctx->loopEnd = 0;
+ ctx->loopStart = 0;
+ return;
+}
+
+static int ComputeAtracStateAndInitSecondBuffer(SceAtracIdInfo *info, u32 readSize, u32 bufferSize) {
+ AtracStatus state;
+ int loopEndFileOffset;
+ int loopEnd;
+
+ if (bufferSize < (u32)info->fileDataEnd) {
+ if (info->streamDataByte < (s32)info->sampleSize * 3) {
+ return 0x80630011;
+ }
+ loopEnd = info->loopEnd;
+ state = ATRAC_STATUS_STREAMED_WITHOUT_LOOP;
+ if ((loopEnd != 0) && (state = ATRAC_STATUS_STREAMED_LOOP_FROM_END, loopEnd != info->endSample)) {
+ loopEndFileOffset = ComputeLoopEndFileOffset(*info, loopEnd);
+ loopEnd = (loopEndFileOffset - info->dataOff) + 1;
+ info->state = ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER;
+ if (loopEnd < info->streamDataByte) {
+ info->streamDataByte = loopEnd;
+ }
+ info->secondStreamOff = 0;
+ info->secondBuffer = 0;
+ info->secondBufferByte = 0;
+ return 0;
+ }
+ } else {
+ state = ATRAC_STATUS_HALFWAY_BUFFER;
+ if (readSize >= (u32)info->fileDataEnd) {
+ state = ATRAC_STATUS_ALL_DATA_LOADED;
+ }
+ }
+ info->state = state;
+ return 0;
+}
+
+int InitContextFromTrackInfo(SceAtracContext *ctx, const TrackInfo *wave, u32 bufferAddr, int readSize, int bufferSize) {
+ (ctx->info).numChan = (char)wave->numChans;
+ const int extraSamples = (ctx->info).codec == 0x1001 ? 0x45 : 0x170;
+ (ctx->info).firstValidSample = extraSamples + wave->firstSampleOffset;
+ int endSample3 = wave->endSample;
+ (ctx->info).sampleSize = wave->blockAlign;
+ InitLengthAndLoop(&ctx->info, endSample3, wave->waveDataSize, wave->firstSampleOffset, wave->loopStart,
+ wave->loopEnd);
+ const int dataOff = wave->dataOff;
+ const int endSample = (ctx->info).endSample;
+ (ctx->info).streamDataByte = readSize - dataOff;
+ (ctx->info).buffer = bufferAddr;
+ (ctx->info).curFileOff = dataOff;
+ (ctx->info).dataOff = dataOff;
+ (ctx->info).fileDataEnd = wave->waveDataSize + dataOff;
+ (ctx->info).curBuffer = 0;
+ (ctx->info).bufferByte = bufferSize;
+ (ctx->info).streamOff = dataOff;
+ if ((ctx->info).loopEnd > endSample) {
+ return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
+ }
+
+ const int numChunks = (endSample >> (0x100b - (ctx->info).codec & 0x1f));
+
+ if ((numChunks * (u32)(ctx->info).sampleSize < (wave->waveDataSize + dataOff) - dataOff)) {
+ int retval = ComputeAtracStateAndInitSecondBuffer(&ctx->info, readSize, bufferSize);
+ if (retval < 0) {
+ return retval;
+ }
+ if ((ctx->info).codec != PSP_CODEC_AT3) {
+ // At3plus
+ // Configure the codec for the sample size, or whatever that data is.
+ (ctx->codec).unk40 = wave->sampleSizeMaybe;
+ (ctx->codec).unk48 = 0;
+ (ctx->codec).unk41 = wave->tailFlag;
+ return 0;
+ }
+ // At3. Set up the hardware codec (hopefully we can correctly support this in sceAudiocodec and thus sceAtrac LLE in the future)
+ // This is not actually necessary since we don't use the actual hardware codec.
+ for (int counter = 4; counter >= 0; counter--) {
+ if ((g_at3BitrateMeta[counter].sampleSize == (ctx->info).sampleSize) &&
+ ((int)g_at3BitrateMeta[counter].dataByte == wave->sampleSizeMaybe)) {
+ (ctx->codec).unk40 = (char)g_at3BitrateMeta[counter].jointStereo;
+ (ctx->codec).unk41 = 0;
+ (ctx->codec).unk42 = 0;
+ (ctx->codec).unk43 = 0;
+ return 0;
+ }
+ }
+ return 0;
+ } else {
+ return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
+ }
+}
+
int Atrac2::RemainingFrames() const {
const SceAtracIdInfo &info = context_->info;
@@ -835,105 +960,27 @@ u32 Atrac2::DecodeInternal(u32 outbufAddr, int *SamplesNum, int *finish) {
}
int Atrac2::SetData(const Track &track, u32 bufferAddr, u32 readSize, u32 bufferSize, int outputChannels) {
- // 72 is about the size of the minimum required data to even be valid.
- if (readSize < 72) {
- return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
+ TrackInfo trackInfo{};
+ if (bufferAddr) {
+ int retval = ParseWaveAT3(Memory::GetPointerRange(bufferAddr, bufferSize), readSize, &trackInfo);
+ if (retval < 0) {
+ ERROR_LOG(Log::Atrac, "Atrac2::SetData: ParseWaveAT3 failed with %08x", retval);
+ return retval;
+ }
}
- // TODO: Check the range (addr, size) instead.
- if (!Memory::IsValidAddress(bufferAddr)) {
- return SCE_KERNEL_ERROR_ILLEGAL_ADDRESS;
+ int retval = InitContextFromTrackInfo(context_, &trackInfo, bufferAddr, readSize, bufferSize);
+ if (retval < 0) {
+ ERROR_LOG(Log::Atrac, "Atrac2::SetData: InitContextFromTrackInfo failed with %08x", retval);
+ return retval;
}
- track.DebugLog();
-
SceAtracIdInfo &info = context_->info;
- if (track.codecType != PSP_CODEC_AT3 && track.codecType != PSP_CODEC_AT3PLUS) {
- // Shouldn't have gotten here, Analyze() checks this.
- ERROR_LOG(Log::Atrac, "unexpected codec type %d in set data", track.codecType);
- return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
- }
-
- if (outputChannels != track.channels) {
- INFO_LOG(Log::Atrac, "Atrac2::SetData: outputChannels %d doesn't match track_.channels %d, decoder will expand.", outputChannels, track.channels);
- }
-
- if (readSize >= track.fileSize) {
- INFO_LOG(Log::Atrac, "The full file was set directly - we can dump it.");
- char filename[512];
- snprintf(filename, sizeof(filename), "%s_%d%s", track.codecType == PSP_CODEC_AT3 ? "at3" : "at3plus", track.endSample, track.channels == 1 ? "_mono" : "");
- DumpFileIfEnabled(Memory::GetPointer(bufferAddr), readSize, filename, DumpFileType::Atrac3);
- } else if (false && (int)g_Config.iDumpFileTypes & (int)DumpFileType::Atrac3) {
- // TODO: This is disabled (using the false above) until we can properly append streamed data to the dump.
- // Still can be useful for debugging by dumping the start of the file, so leaving it here.
- dumpBuffer_.resize(readSize);
- Memory::Memcpy(dumpBuffer_.data(), bufferAddr, readSize);
- }
-
- CreateDecoder(track.codecType, track.bytesPerFrame, track.channels);
+ CreateDecoder(info.codec, info.sampleSize, info.numChan);
outputChannels_ = outputChannels;
- if (!track.loopinfo.empty()) {
- info.loopNum = track.loopinfo[0].playCount;
- info.loopStart = track.loopStartSample;
- info.loopEnd = track.loopEndSample;
- } else {
- info.loopNum = 0;
- info.loopStart = 0;
- info.loopEnd = 0;
- }
-
- context_->codec.inBuf = bufferAddr;
-
- if (readSize > track.fileSize) {
- INFO_LOG(Log::Atrac, "readSize (%d) > track_.fileSize (%d)", readSize, track.fileSize);
- }
-
- if (bufferSize >= track.fileSize) {
- // Buffer is big enough to fit the whole track.
- if (readSize < bufferSize) {
- info.state = ATRAC_STATUS_HALFWAY_BUFFER;
- } else {
- info.state = ATRAC_STATUS_ALL_DATA_LOADED;
- }
- } else {
- // Streaming cases with various looping types.
- if (track.loopEndSample <= 0) {
- // There's no looping, but we need to stream the data in our buffer.
- info.state = ATRAC_STATUS_STREAMED_WITHOUT_LOOP;
- } else if (track.loopEndSample == track.endSample + track.FirstSampleOffsetFull()) {
- info.state = ATRAC_STATUS_STREAMED_LOOP_FROM_END;
- } else {
- info.state = ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER;
- }
- }
-
- DEBUG_LOG(Log::Atrac, "Atrac mode setup: %s", AtracStatusToString(info.state));
-
- // Copy parameters into state struct.
- info.codec = track.codecType;
- info.numChan = track.channels;
- info.sampleSize = track.bytesPerFrame;
- info.buffer = bufferAddr;
- info.bufferByte = bufferSize;
- info.firstValidSample = track.FirstSampleOffsetFull();
- info.endSample = track.endSample + info.firstValidSample;
- if (track.loopStartSample != 0xFFFFFFFF) {
- info.loopStart = track.loopStartSample;
- info.loopEnd = track.loopEndSample;
- // Sanity check the loop points, useful for testing.
- }
- info.dataOff = track.dataByteOffset;
- info.curFileOff = track.dataByteOffset;
- info.streamOff = track.dataByteOffset;
- info.streamDataByte = readSize - info.dataOff;
- info.fileDataEnd = track.fileSize;
- info.decodePos = track.FirstSampleOffsetFull();
- info.numSkipFrames = info.firstValidSample / info.SamplesPerFrame();
- // NOTE: we do not write into secondBuffer/secondBufferByte! they linger...
-
INFO_LOG(Log::Atrac,
"Atrac: sampleSize: %d buffer: %08x bufferByte: %d firstValidSample: %d\n"
"endSample: %d loopStart: %d loopEnd: %d\n"
@@ -946,7 +993,7 @@ int Atrac2::SetData(const Track &track, u32 bufferAddr, u32 readSize, u32 buffer
);
int skipCount = 0; // TODO: use for delay
- u32 retval = SkipFrames(&skipCount);
+ retval = SkipFrames(&skipCount);
// Seen in Mui Mui house. Things go very wrong after this..
if (retval == SCE_ERROR_ATRAC_API_FAIL) {
@@ -961,26 +1008,28 @@ int Atrac2::SetData(const Track &track, u32 bufferAddr, u32 readSize, u32 buffer
void Atrac2::WrapLastPacket() {
SceAtracIdInfo &info = context_->info;
- // We need to handle wrapping the overshot partial packet at the end.
- if (AtracStatusIsStreaming(info.state)) {
- // This logic is similar to GetStreamDataInfo.
- int distanceToEnd = RoundDownToMultiple(info.bufferByte - info.streamOff, info.sampleSize);
- if (info.streamDataByte < distanceToEnd) {
- // There's space left without wrapping. Don't do anything.
- // INFO_LOG(Log::Atrac, "Packets fit into the buffer fully. %08x < %08x", readSize, bufferSize);
- // In this case, seems we need to zero some bytes. In one test, this seems to be 336.
- // Maybe there's a logical bug and the copy happens even when not needed, it's just that it'll
- // copy zeroes. Either way, let's just copy some bytes to make our sanity check hexdump pass.
- Memory::Memset(info.buffer, 0, 128);
- } else {
- // Wraps around.
- const int copyStart = info.streamOff + distanceToEnd;
- const int copyLen = info.bufferByte - copyStart;
- // Then, let's copy it.
- DEBUG_LOG(Log::Atrac, "Packets didn't fit evenly. Last packet got split into %d/%d (sum=%d). Copying to start of buffer.",
- copyLen, info.sampleSize - copyLen, info.sampleSize);
- Memory::Memcpy(info.buffer, info.buffer + copyStart, copyLen);
- }
+ // If streaming, we need to handle wrapping the overshot partial packet at the end. If not we don't.
+ if (!AtracStatusIsStreaming(info.state)) {
+ return;
+ }
+
+ // This logic is similar to GetStreamDataInfo.
+ int distanceToEnd = RoundDownToMultiple(info.bufferByte - info.streamOff, info.sampleSize);
+ if (info.streamDataByte < distanceToEnd) {
+ // There's space left without wrapping. Don't do anything.
+ // INFO_LOG(Log::Atrac, "Packets fit into the buffer fully. %08x < %08x", readSize, bufferSize);
+ // In this case, seems we need to zero some bytes. In one test, this seems to be 336.
+ // Maybe there's a logical bug and the copy happens even when not needed, it's just that it'll
+ // copy zeroes. Either way, let's just copy some bytes to make our sanity check hexdump pass.
+ Memory::Memset(info.buffer, 0, 128);
+ } else {
+ // Wraps around.
+ const int copyStart = info.streamOff + distanceToEnd;
+ const int copyLen = info.bufferByte - copyStart;
+ // Then, let's copy it.
+ DEBUG_LOG(Log::Atrac, "Packets didn't fit evenly. Last packet got split into %d/%d (sum=%d). Copying to start of buffer.",
+ copyLen, info.sampleSize - copyLen, info.sampleSize);
+ Memory::Memcpy(info.buffer, info.buffer + copyStart, copyLen);
}
}
@@ -1051,13 +1100,7 @@ u32 Atrac2::GetInternalCodecError() const {
int Atrac2::Bitrate() const {
const SceAtracIdInfo &info = context_->info;
-
- int bitrate = (info.sampleSize * 352800) / 1000;
- if (info.codec == PSP_CODEC_AT3PLUS)
- bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0;
- else
- bitrate = (bitrate + 511) >> 10;
- return bitrate;
+ return info.BitRate();
}
void Atrac2::InitLowLevel(const Atrac3LowLevelParams ¶ms, int codecType) {
diff --git a/Core/HLE/AtracCtx2.h b/Core/HLE/AtracCtx2.h
index f1645a0699..f47f9a03bf 100644
--- a/Core/HLE/AtracCtx2.h
+++ b/Core/HLE/AtracCtx2.h
@@ -2,7 +2,7 @@
#include
-#include "Core/HLE/AtracCtx.h"
+#include "Core/HLE/AtracBase.h"
class Atrac2 : public AtracBase {
public:
diff --git a/Core/HLE/sceAtrac.h b/Core/HLE/sceAtrac.h
index 594b68c194..42e61576ab 100644
--- a/Core/HLE/sceAtrac.h
+++ b/Core/HLE/sceAtrac.h
@@ -19,6 +19,7 @@
#include "Core/HLE/sceAudiocodec.h"
#include "Core/Util/AtracTrack.h"
+#include "Core/HLE/AtracCtx.h"
class PointerWrap;
@@ -31,104 +32,6 @@ int __AtracMaxContexts();
void __AtracNotifyLoadModule(int version, u32 crc, u32 bssAddr, int bssSize);
void __AtracNotifyUnloadModule();
-// The "state" member of SceAtracIdInfo.
-enum AtracStatus : u8 {
- ATRAC_STATUS_UNINITIALIZED = 0, // bad state
-
- ATRAC_STATUS_NO_DATA = 1,
-
- // The entire file is loaded into memory, no further file access needed.
- ATRAC_STATUS_ALL_DATA_LOADED = 2,
-
- // The buffer is sized to fit the entire file, but it's only partially loaded, so you can start playback before loading the whole file.
- ATRAC_STATUS_HALFWAY_BUFFER = 3,
-
- // In these ones, the buffer is smaller than the file, and data is streamed into it as needed for playback.
- // These are the most complex modes, both to implement and use.
- ATRAC_STATUS_STREAMED_WITHOUT_LOOP = 4,
- ATRAC_STATUS_STREAMED_LOOP_FROM_END = 5,
- // This means there's additional audio after the loop.
- // i.e. ~~before loop~~ [ ~~this part loops~~ ] ~~after loop~~
- // The "fork in the road" means a second buffer is needed for the second path.
- ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER = 6,
-
- // In this mode, the only API to call is sceAtracLowLevelDecode, which decodes a stream packet by packet without any other metadata.
- ATRAC_STATUS_LOW_LEVEL = 8,
-
- // This mode is for using an Atrac context as the audio source for an sceSas channel. Not used a lot (Sol Trigger).
- ATRAC_STATUS_FOR_SCESAS = 16,
-
- // Bitwise-and the status with this to check for any of the streaming modes in a single test.
- ATRAC_STATUS_STREAMED_MASK = 4,
-};
-
-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.
- s32 endSample; // Last sample index of the track.
- s32 loopStart; // Start of the loop (sample index)
- s32 loopEnd; // End of the loop (sample index)
- s32 firstValidSample; // Seems to be the number of skipped samples at the start. After SetID, decodePos will match this. Was previously misnamed 'samplesPerChan'.
- u8 numSkipFrames; // This is 1 for a single frame when a loop is triggered, otherwise seems to stay at 0. Likely mis-named.
- AtracStatus state; // State enum, see AtracStatus.
- u8 curBuffer; // Current buffer (1 == second, 2 == done?) Previously unk
- u8 numChan; // Number of audio channels, usually 2 but 1 is possible.
- u16 sampleSize; // Size in bytes of an encoded audio frame.
- u16 codec; // Codec. 0x1000 is Atrac3+, 0x1001 is Atrac3. See the PSP_CODEC_ enum (only these two are supported).
- s32 dataOff; // File offset in bytes where the Atrac3+ frames start appearing. The first dummy packet starts here.
- s32 curFileOff; // File offset in bytes corresponding to the start of next packet that will be *decoded* (on the next call to sceAtracDecodeData).
- s32 fileDataEnd; // File size in bytes.
- s32 loopNum; // Current loop counter. If 0, will not loop. -1 loops for ever, positive numbers get decremented on the loop end. So to play a song 3 times and then end, set this to 2.
- s32 streamDataByte; // Number of bytes of queued/buffered/uploaded data. In full and half-way modes, this isn't decremented as you decode.
- s32 streamOff; // Streaming modes only: The byte offset inside the RAM buffer where sceAtracDecodeData will read from next. ONLY points to even packet boundaries.
- s32 secondStreamOff; // A kind of stream position in the secondary buffer.
- u32 buffer; // Address in RAM of the main buffer.
- u32 secondBuffer; // Address in RAM of the second buffer, or 0 if not used.
- u32 bufferByte; // Size in bytes of the main buffer.
- u32 secondBufferByte; // Size in bytes of the second buffer.
- // Offset 72 here.
- // make sure the size is 128
- u32 unk[14];
-
- // Simple helpers. Similar ones are on track_, but we shouldn't need track_ anymore when playing back.
-
- int SamplesPerFrame() const {
- return codec == 0x1000 ? ATRAC3PLUS_MAX_SAMPLES : ATRAC3_MAX_SAMPLES;
- }
- int SamplesFrameMask() const {
- return SamplesPerFrame() - 1;
- }
- int SkipSamples() const {
- // These first samples are skipped, after first possibly skipping 0-2 full frames, it seems.
- return codec == 0x1000 ? 0x170 : 0x45;
- }
-};
-
-// One of these structs is allocated for each Atrac context.
-// The raw codec state is stored in 'codec'.
-// The internal playback state is stored in 'info', and that is used for all state keeping in the Atrac2 implementation,
-// imitating what happens on hardware as closely as possible.
-struct SceAtracContext {
- // size 128
- SceAudiocodecCodec codec;
- // size 128
- SceAtracIdInfo info;
-};
-
-struct Atrac3LowLevelParams {
- int encodedChannels;
- int outputChannels;
- int bytesPerFrame;
-};
-
constexpr int PSP_MAX_ATRAC_IDS = 6;
class AtracBase;
diff --git a/Core/HLE/sceAudiocodec.cpp b/Core/HLE/sceAudiocodec.cpp
index bb3ad5022e..a8ae37276a 100644
--- a/Core/HLE/sceAudiocodec.cpp
+++ b/Core/HLE/sceAudiocodec.cpp
@@ -50,7 +50,14 @@ static_assert(sizeof(SceAudiocodecCodec) == 128);
// Known byte values.
// Atrac3 (0x1001)
-// ------------------------------------------------
+//
+// Frame size data byte JointStereo?
+// -------------------------------------------------
+// 0x180 0x04 0
+// 0x130 0x06 0
+// 0x0C0 0x0B 1
+// 0x0C0 0x0E 0
+// 0x098 0x0F 0
// AAC (0x1003)
// ------------------------------------------------
diff --git a/Core/HLE/sceAudiocodec.h b/Core/HLE/sceAudiocodec.h
index 4d24a98e2b..ac3aaf9c45 100644
--- a/Core/HLE/sceAudiocodec.h
+++ b/Core/HLE/sceAudiocodec.h
@@ -44,8 +44,8 @@ struct SceAudiocodecCodec {
// Probably, from here on out is a union with different fields for different codecs.
union { // offset 40 / 0x28
struct {
- s8 unk40; // 28 format or looping related
- s8 unk41; // 29 format or looping related
+ s8 unk40; // 28 format or looping related . Aka tailrelated
+ s8 unk41; // 29 format or looping related . Aka tailflag
s8 unk42;
s8 unk43;
};
diff --git a/Core/MIPS/JitCommon/JitBlockCache.cpp b/Core/MIPS/JitCommon/JitBlockCache.cpp
index a68c52a274..1eb7020d66 100644
--- a/Core/MIPS/JitCommon/JitBlockCache.cpp
+++ b/Core/MIPS/JitCommon/JitBlockCache.cpp
@@ -120,10 +120,11 @@ void JitBlockCache::Shutdown() {
// This clears the JIT cache. It's called from JitCache.cpp when the JIT cache
// is full and when saving and loading states.
void JitBlockCache::Clear() {
+ // Note: We intentionally clear the block_map_ first to avoid O(N^2) behavior in RemoveBlockMap
+ block_map_.clear();
for (int i = 0; i < num_blocks_; i++) {
DestroyBlock(i, DestroyType::CLEAR);
}
- block_map_.clear();
proxyBlockMap_.clear();
links_to_.clear();
num_blocks_ = 0;
@@ -238,6 +239,7 @@ void JitBlockCache::RemoveBlockMap(int block_num) {
// TODO: This is O(n), so O(n^2) when called for every block.
for (auto it = block_map_.begin(); it != block_map_.end(); ++it) {
if (it->second == (u32)block_num) {
+ _dbg_assert_(false);
block_map_.erase(it);
break;
}
diff --git a/Core/Util/AtracTrack.cpp b/Core/Util/AtracTrack.cpp
index 86586ba863..f2a00fd5f9 100644
--- a/Core/Util/AtracTrack.cpp
+++ b/Core/Util/AtracTrack.cpp
@@ -2,18 +2,20 @@
#include "Common/StringUtils.h"
#include "Core/Util/AtracTrack.h"
#include "Core/HLE/ErrorCodes.h"
+#include "Common/Data/Format/RIFF.h"
#include "Core/MemMap.h"
// Atrac file parsing constants
-#define AT3_MAGIC 0x0270
-#define AT3_PLUS_MAGIC 0xFFFE // This is normally a marker for WAVE_FORMAT_EXTENSIBLE
-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;
+constexpr int RIFF_CHUNK_MAGIC = 0x46464952;
+constexpr int RIFF_WAVE_MAGIC = 0x45564157;
+constexpr int FMT_CHUNK_MAGIC = 0x20746D66;
+constexpr int DATA_CHUNK_MAGIC = 0x61746164;
+constexpr int SMPL_CHUNK_MAGIC = 0x6C706D73;
+constexpr int FACT_CHUNK_MAGIC = 0x74636166;
+
+constexpr u32 WAVE_FORMAT_EXTENSIBLE = 0xFFFE;
+constexpr u32 WAVE_FORMAT_AT3 = 0x270;
static u16 Read16(const u8 *buffer, int offset) {
u16 value;
@@ -27,6 +29,7 @@ static u32 Read32(const u8 *buffer, int offset) {
return value;
}
+// Old WAVE parser.
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.
if (size < 72) {
@@ -108,14 +111,14 @@ int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *err
}
auto at3fmt = (const RIFFFmtChunk *)(buffer + offset);
- if (chunkSize < 32 || (at3fmt->fmtTag == AT3_PLUS_MAGIC && chunkSize < 52)) {
+ if (chunkSize < 32 || (at3fmt->fmtTag == WAVE_FORMAT_EXTENSIBLE && chunkSize < 52)) {
*error = "AnalyzeTrack: fmt definition too small(%d)";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
- if (at3fmt->fmtTag == AT3_MAGIC)
+ if (at3fmt->fmtTag == WAVE_FORMAT_AT3)
track->codecType = PSP_CODEC_AT3;
- else if (at3fmt->fmtTag == AT3_PLUS_MAGIC)
+ else if (at3fmt->fmtTag == WAVE_FORMAT_EXTENSIBLE)
track->codecType = PSP_CODEC_AT3PLUS;
else {
*error = "AnalyzeTrack: invalid fmt magic: %04x";
@@ -140,13 +143,12 @@ int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *err
// TODO: There are some format specific bytes here which seem to have fixed values?
// Probably don't need them.
- if (at3fmt->fmtTag == AT3_MAGIC) { // 0x270
+ if (at3fmt->fmtTag == WAVE_FORMAT_AT3) { // 0x270
// 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);
- } else if (at3fmt->fmtTag == AT3_PLUS_MAGIC) {
- _dbg_assert_(at3fmt->fmtTag == 0xFFFE);
+ } else if (at3fmt->fmtTag == WAVE_FORMAT_EXTENSIBLE) {
// It's in an "Extensible" wave format. Let's read some more.
}
if (chunkSize > 16) {
@@ -321,3 +323,217 @@ int AnalyzeAA3Track(const u8 *buffer, u32 size, u32 fileSize, Track *track, std:
track->endSample -= 1;
return 0;
}
+
+static inline u32 Read16(const u8 *base, int *offset) {
+ // Little-endian reliance.
+ u32 value = 0;
+ memcpy(&value, base + *offset, 2);
+ *offset += 2;
+ return value;
+}
+
+static inline u32 Read32(const u8 *base, int *offset) {
+ // Little-endian reliance.
+ u32 value = 0;
+ memcpy(&value, base + *offset, 4);
+ *offset += 4;
+ return value;
+}
+
+static const u8 g_atrac3Checkbytes[] = {
+ 0xBF, 0xAA, 0x23, 0xE9, 0x58, 0xCB, 0x71, 0x44,
+ 0xA1, 0x19, 0xFF, 0xFA, 0x01, 0xE4, 0xCE, 0x62,
+};
+
+static inline int RoundUpToEven(int size) {
+ // Round up to the next even number.
+ return (size + 1) & ~1;
+}
+
+int ParseWaveAT3(const u8 *data, int dataLength, TrackInfo *track) {
+ track->loopStart = 0xFFFFFFFF;
+ track->loopEnd = 0xFFFFFFFF;
+ track->firstSampleOffset = 0;
+ track->endSample = 0;
+ track->waveDataSize = 0;
+
+ int retval = SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ int offset = 0; // byte offset into the data array. Is kept even.
+
+ // Scan RIFF chunks for the RIFFWAVE header. Normally we find this immediately.
+ while (true) {
+ if (offset + 0xC >= dataLength) {
+ return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
+ }
+ const u32 blockID = Read32(data, &offset);
+ if (blockID != RIFF_CHUNK_MAGIC) {
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ const u32 blockSize = RoundUpToEven(Read32(data, &offset));
+ const u32 waveID = Read32(data, &offset);
+ if (waveID == RIFF_WAVE_MAGIC) {
+ // We found the WAVE header.
+ break;
+ }
+ offset += blockSize - 4;
+ }
+
+ // Now that we have the header, loop through the rest of the chunks.
+ bool modifiedSampleOffset = false;
+ while (true) {
+ if (offset + 8 >= dataLength) {
+ return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
+ }
+ const u32 chunkID = Read32(data, &offset);
+ const int chunkSize = RoundUpToEven(Read32(data, &offset));
+ const int nextOffset = offset + chunkSize;
+ // We allow the data chunk to be bigger than readSize, as we may not have read the whole file.
+ if (offset + chunkSize > dataLength && chunkID != DATA_CHUNK_MAGIC) {
+ return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
+ }
+ int remainingBytes = 0;
+ switch (chunkID) {
+ case DATA_CHUNK_MAGIC:
+ {
+ // This should be the last chunk we find, it lasts to the end of the file.
+ // I guess another order is possible, but not if streaming.
+ track->waveDataSize = chunkSize;
+ track->dataOff = offset;
+ if (!track->firstSampleOffset) {
+ INFO_LOG(Log::Atrac, "DATA chunk found at offset %d with size %d", offset, chunkSize);
+ track->firstSampleOffset = retval == PSP_CODEC_AT3 ? 0x400 : 0x800;
+ }
+ if (modifiedSampleOffset && retval == PSP_CODEC_AT3PLUS) {
+ track->firstSampleOffset -= 0xb8;
+ if (track->loopEnd != 0xFFFFFFFF) {
+ track->loopEnd -= 0xb8;
+ track->loopStart -= 0xb8;
+ }
+ }
+ return retval;
+ }
+ case FMT_CHUNK_MAGIC:
+ {
+ if (retval != SCE_ERROR_ATRAC_UNKNOWN_FORMAT) {
+ // This means dual FMT chunks, which is not valid.
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ if (chunkSize < 0x20) {
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ const u16 fmtTag = Read16(data, &offset);
+ const u16 chans = Read16(data, &offset);
+ track->numChans = chans;
+ if (chans != 1 && chans != 2) {
+ // Only stereo and mono are supported.
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ const int sampleRate = Read32(data, &offset);
+ if (sampleRate != 44100) {
+ // Only a single sample rate is supported.
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ offset += 4; // skip avgBytesPerSec
+ track->blockAlign = (u16)Read16(data, &offset);
+ if (track->blockAlign == 0) {
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ if (fmtTag == WAVE_FORMAT_AT3) {
+ // Atrac3 format.
+ offset += 4; // skip the extra bytes
+ const u16 jointStereo = Read16(data, &offset);
+ if (jointStereo != 1) {
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ offset += 4;
+ const u16 sampleSizeTailFlag = Read16(data, &offset);
+ track->sampleSizeMaybe = (u8)sampleSizeTailFlag;
+ track->tailFlag = (u8)(sampleSizeTailFlag >> 8);
+ const u16 unknown2 = Read16(data, &offset);
+ if ((sampleSizeTailFlag & 0xffff) != unknown2) {
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ // Don't what this is, but it seems to be always 1.
+ const u32 supposedToBeOne = Read32(data, &offset);
+ if (supposedToBeOne != 1) {
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ retval = PSP_CODEC_AT3; // ATRAC3
+ remainingBytes = chunkSize - 0x20;
+ } else if (fmtTag == WAVE_FORMAT_EXTENSIBLE) {
+ // Atrac3+ format, probably.
+ if (chunkSize < 0x34) {
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ if (memcmp(data + offset + 10, g_atrac3Checkbytes, sizeof(g_atrac3Checkbytes)) != 0) {
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ track->sampleSizeMaybe = data[offset + 0x1c];
+ track->tailFlag = data[offset + 0x1d];
+ if ((track->sampleSizeMaybe << 27) >> 29 != track->numChans) {
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ offset += 0x26; // Or, use the blocksize?
+ retval = PSP_CODEC_AT3PLUS;
+ remainingBytes = chunkSize - 0x34;
+ } else {
+ // Unsupported fmtTag.
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ if (remainingBytes) {
+ INFO_LOG(Log::Atrac, "FMT chunk has %d extra bytes", remainingBytes);
+ }
+ break;
+ }
+ case SMPL_CHUNK_MAGIC:
+ {
+ if ((int)track->loopStart < 0) {
+ if (chunkSize < 0x20) {
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ offset += 0x1c;
+ const int numLoops = Read32(data, &offset);
+ if (numLoops != 0) {
+ if (chunkSize < 0x34) {
+ return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
+ }
+ offset += 0xc;
+ track->loopStart = Read32(data, &offset);
+ track->loopEnd = Read32(data, &offset);
+ if (track->loopEnd <= (int)track->loopStart) {
+ return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
+ }
+ }
+ }
+ break;
+ }
+ case FACT_CHUNK_MAGIC:
+ {
+ if (chunkSize < 4) {
+ return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
+ }
+ track->endSample = Read32(data, &offset);
+ remainingBytes = chunkSize - 4;
+ if (remainingBytes == 4) {
+ track->firstSampleOffset = Read32(data, &offset);
+ remainingBytes = 0;
+ } else if (remainingBytes >= 8) {
+ // Extended FACT format.
+ offset += 4;
+ track->firstSampleOffset = Read32(data, &offset);
+ modifiedSampleOffset = true;
+ remainingBytes -= 8;
+ }
+ break;
+ }
+ default:
+ // Skip the unknown block.
+ INFO_LOG(Log::Atrac, "Skipping unknown block ID %08x at offset %d with size %d", chunkID, offset - 8, chunkSize);
+ break;
+ }
+ if (remainingBytes) {
+ INFO_LOG(Log::Atrac, "%08x chunk has %d extra bytes", chunkID, remainingBytes);
+ }
+ offset = nextOffset;
+ }
+}
diff --git a/Core/Util/AtracTrack.h b/Core/Util/AtracTrack.h
index 1d670865b4..5090d61f00 100644
--- a/Core/Util/AtracTrack.h
+++ b/Core/Util/AtracTrack.h
@@ -6,9 +6,7 @@
#include
#include "Core/HLE/sceAudiocodec.h"
-
-constexpr u32 ATRAC3_MAX_SAMPLES = 0x400; // 1024
-constexpr u32 ATRAC3PLUS_MAX_SAMPLES = 0x800; // 2048
+#include "Core/HLE/AtracBase.h"
struct AtracLoopInfo {
int cuePointID;
@@ -108,3 +106,19 @@ struct Track {
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);
+
+struct TrackInfo {
+ u16 numChans;
+ u16 blockAlign;
+ u8 sampleSizeMaybe;
+ u8 tailFlag;
+ u8 unused[2];
+ u32 dataOff;
+ u32 endSample;
+ u32 waveDataSize;
+ u32 firstSampleOffset;
+ u32 loopStart;
+ u32 loopEnd;
+};
+
+int ParseWaveAT3(const u8 *data, int length, TrackInfo *track);
diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp
index 9aa5f34c10..ea2d0fe18a 100644
--- a/UI/GameSettingsScreen.cpp
+++ b/UI/GameSettingsScreen.cpp
@@ -1,3 +1,4 @@
+
// Copyright (c) 2013- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify