mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Merge pull request #20571 from hrydgard/more-atrac-refactor
More Atrac3 refactor
This commit is contained in:
1 parent
db5ef6fb87
commit
cc83071c2f
15 files changed
+681
-371
No files matched your search
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 ¶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;
|
||||
};
|
||||
@@ -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
@@ -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 ¶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:
|
||||
|
||||
+162
-119
@@ -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) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Core/HLE/AtracCtx.h"
|
||||
#include "Core/HLE/AtracBase.h"
|
||||
|
||||
class Atrac2 : public AtracBase {
|
||||
public:
|
||||
|
||||
+1
-98
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
// ------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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,3 +1,4 @@
|
||||
|
||||
// Copyright (c) 2013- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
|
||||
Reference in new issue
Block a user