Merge pull request #20571 from hrydgard/more-atrac-refactor

More Atrac3 refactor
This commit is contained in:
Henrik Rydgård committed 2025-07-09 16:34:13 +02:00
1 parent db5ef6fb87
commit cc83071c2f
15 files changed
+681 -371

No files matched your search

+1
View File
@@ -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
+1
View File
@@ -1190,6 +1190,7 @@
<ClInclude Include="FileLoaders\ZipFileLoader.h" />
<ClInclude Include="FileSystems\BlobFileSystem.h" />
<ClInclude Include="FrameTiming.h" />
<ClInclude Include="HLE\AtracBase.h" />
<ClInclude Include="HLE\AtracCtx.h" />
<ClInclude Include="HLE\AtracCtx2.h" />
<ClInclude Include="HLE\ErrorCodes.h" />
+3
View File
@@ -2190,6 +2190,9 @@
<ClInclude Include="FileLoaders\ZipFileLoader.h">
<Filter>FileLoaders</Filter>
</ClInclude>
<ClInclude Include="HLE\AtracBase.h">
<Filter>HLE\Libraries</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE.TXT" />
+248
View File
@@ -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<SceAtracContext> 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 &params, 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;
};
+1 -2
View File
@@ -28,8 +28,7 @@
#include "Core/HLE/AtracCtx.h"
#include "Core/HW/Atrac3Standalone.h"
#include "Core/HLE/sceKernelMemory.h"
#include <sstream>
#include <iomanip>
const size_t overAllocBytes = 16384;
+2 -130
View File
@@ -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<SceAtracContext> 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 &params, 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:
+162 -119
View File
@@ -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 &params, int codecType) {
+1 -1
View File
@@ -2,7 +2,7 @@
#include <cstdint>
#include "Core/HLE/AtracCtx.h"
#include "Core/HLE/AtracBase.h"
class Atrac2 : public AtracBase {
public:
+1 -98
View File
@@ -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;
+8 -1
View File
@@ -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)
// ------------------------------------------------
+2 -2
View File
@@ -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;
};
+3 -1
View File
@@ -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;
}
+230 -14
View File
@@ -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;
}
}
+17 -3
View File
@@ -6,9 +6,7 @@
#include <string>
#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);
+1
View File
@@ -1,3 +1,4 @@
// Copyright (c) 2013- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify