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