Skip to content

Reimplement Atrac-through-SAS #20156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 25, 2025
34 changes: 19 additions & 15 deletions Core/HLE/AtracCtx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -571,21 +571,6 @@ int Atrac::AddStreamData(u32 bytesToAdd) {
return 0;
}

u32 Atrac::AddStreamDataSas(u32 bufPtr, u32 bytesToAdd) {
int addbytes = std::min(bytesToAdd, track_.fileSize - first_.fileoffset - track_.FirstOffsetExtra());
Memory::Memcpy(dataBuf_ + first_.fileoffset + track_.FirstOffsetExtra(), bufPtr, addbytes, "AtracAddStreamData");
first_.size += bytesToAdd;
if (first_.size >= track_.fileSize) {
first_.size = track_.fileSize;
if (bufferState_ == ATRAC_STATUS_HALFWAY_BUFFER)
bufferState_ = ATRAC_STATUS_ALL_DATA_LOADED;
}
first_.fileoffset += addbytes;
// refresh context_
WriteContextToPSPMem();
return 0;
}

u32 Atrac::GetNextSamples() {
if (currentSample_ >= track_.endSample) {
return 0;
Expand Down Expand Up @@ -961,6 +946,25 @@ int Atrac::DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData, i
return 0;
}

void Atrac::CheckForSas() {
SetOutputChannels(1);
}

int Atrac::EnqueueForSas(u32 bufPtr, u32 bytesToAdd) {
int addbytes = std::min(bytesToAdd, track_.fileSize - first_.fileoffset - track_.FirstOffsetExtra());
Memory::Memcpy(dataBuf_ + first_.fileoffset + track_.FirstOffsetExtra(), bufPtr, addbytes, "AtracAddStreamData");
first_.size += bytesToAdd;
if (first_.size >= track_.fileSize) {
first_.size = track_.fileSize;
if (bufferState_ == ATRAC_STATUS_HALFWAY_BUFFER)
bufferState_ = ATRAC_STATUS_ALL_DATA_LOADED;
}
first_.fileoffset += addbytes;
// refresh context_
WriteContextToPSPMem();
return 0;
}

void Atrac::DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) {
// Hack, but works.
int samplesNum;
Expand Down
26 changes: 23 additions & 3 deletions Core/HLE/AtracCtx.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ struct AtracResetBufferInfo {
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;
Expand Down Expand Up @@ -112,7 +126,6 @@ class AtracBase {

virtual void GetStreamDataInfo(u32 *writePtr, u32 *writableBytes, u32 *readOffset) = 0;
virtual int AddStreamData(u32 bytesToAdd) = 0;
virtual u32 AddStreamDataSas(u32 bufPtr, u32 bytesToAdd) = 0;
virtual int ResetPlayPosition(int sample, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf, bool *delay) = 0;
virtual int GetResetBufferInfo(AtracResetBufferInfo *bufferInfo, int sample, bool *delay) = 0;
virtual int SetData(const Track &track, u32 buffer, u32 readSize, u32 bufferSize, int outputChannels) = 0;
Expand All @@ -121,10 +134,15 @@ class AtracBase {
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 void DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) = 0;

virtual u32 GetNextSamples() = 0;
virtual void InitLowLevel(const Atrac3LowLevelParams &params, int codecType) = 0;

virtual void CheckForSas() = 0;
virtual int EnqueueForSas(u32 address, u32 ptr) = 0;
virtual void DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) = 0;
virtual const AtracSasStreamState *StreamStateForSas() const { return nullptr; }

virtual int GetSoundSample(int *endSample, int *loopStartSample, int *loopEndSample) const = 0;

virtual int GetContextVersion() const = 0;
Expand Down Expand Up @@ -205,14 +223,16 @@ class Atrac : public AtracBase {
void GetStreamDataInfo(u32 *writePtr, u32 *writableBytes, u32 *readOffset) override;
// Notify the player that the user has written some new data.
int AddStreamData(u32 bytesToAdd) override;
u32 AddStreamDataSas(u32 bufPtr, u32 bytesToAdd) override;
int ResetPlayPosition(int sample, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf, bool *delay) override;
int GetResetBufferInfo(AtracResetBufferInfo *bufferInfo, int sample, bool *delay) override;
int SetData(const Track &track, u32 buffer, u32 readSize, u32 bufferSize, int outputChannels) override;
int GetSecondBufferInfo(u32 *fileOffset, u32 *desiredSize) override;
int SetSecondBuffer(u32 secondBuffer, u32 secondBufferSize) override;
u32 DecodeData(u8 *outbuf, u32 outbufPtr, int *SamplesNum, int *finish, int *remains) override;
int DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData, int *bytesWritten) override;

void CheckForSas() override;
int EnqueueForSas(u32 address, u32 ptr) override;
void DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) override;

// Returns how many samples the next DecodeData will write.
Expand Down
172 changes: 139 additions & 33 deletions Core/HLE/AtracCtx2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,26 +175,37 @@ Atrac2::Atrac2(u32 contextAddr, int codecType) {
info.state = ATRAC_STATUS_NO_DATA;
info.curBuffer = 0;

sasReadOffset_ = 0;
sasBasePtr_ = 0;
sas_.streamOffset = 0;
sas_.bufPtr[0] = 0;
sas_.bufPtr[1] = 0;
} else {
// We're loading state, we'll restore the context in DoState.
}
}

void Atrac2::DoState(PointerWrap &p) {
auto s = p.Section("Atrac2", 1, 2);
auto s = p.Section("Atrac2", 1, 3);
if (!s)
return;

Do(p, outputChannels_);
// The only thing we need to save now is the outputChannels_ and the context pointer. And technically, not even that since
// it can be computed. Still, for future proofing, let's save it.
Do(p, context_);

// Actually, now we also need to save sas state. I guess this could also be saved on the Sas side, but this is easier.
if (s >= 2) {
Do(p, sasReadOffset_);
Do(p, sasBasePtr_);
Do(p, sas_.streamOffset);
Do(p, sas_.bufPtr[0]);
}
// Added support for streaming sas audio, need some more context state.
if (s >= 3) {
Do(p, sas_.bufPtr[1]);
Do(p, sas_.bufSize[0]);
Do(p, sas_.bufSize[1]);
Do(p, sas_.isStreaming);
Do(p, sas_.curBuffer);
Do(p, sas_.fileOffset);
}

const SceAtracIdInfo &info = context_->info;
Expand Down Expand Up @@ -497,20 +508,6 @@ int Atrac2::AddStreamData(u32 bytesToAdd) {
return 0;
}

u32 Atrac2::AddStreamDataSas(u32 bufPtr, u32 bytesToAdd) {
SceAtracIdInfo &info = context_->info;
// Internal API, seems like a combination of GetStreamDataInfo and AddStreamData, for use when
// an Atrac context is bound to an sceSas channel.
// Sol Trigger is the only game I know that uses this.
_dbg_assert_(false);

u8 *dest = Memory::GetPointerWrite(sasBasePtr_ + sasReadOffset_);
memcpy(dest, Memory::GetPointer(bufPtr), bytesToAdd);
info.buffer += bytesToAdd;
info.streamDataByte += bytesToAdd;
return 0;
}

static int ComputeLoopedStreamWritableBytes(const SceAtracIdInfo &info, const int loopStartFileOffset, const u32 loopEndFileOffset) {
const u32 writeOffset = info.curFileOff + info.streamDataByte;
if (writeOffset >= loopEndFileOffset) {
Expand Down Expand Up @@ -681,6 +678,10 @@ u32 Atrac2::DecodeInternal(u32 outbufAddr, int *SamplesNum, int *finish) {
return SCE_ERROR_ATRAC_BUFFER_IS_EMPTY;
}

if (info.state == ATRAC_STATUS_FOR_SCESAS) {
_dbg_assert_(false);
}

u32 streamOff;
u32 bufferPtr;
if (!AtracStatusIsStreaming(info.state)) {
Expand Down Expand Up @@ -1039,27 +1040,132 @@ int Atrac2::DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData,
return 0;
}

void Atrac2::CheckForSas() {
SceAtracIdInfo &info = context_->info;
if (info.numChan != 1) {
WARN_LOG(Log::ME, "Caller forgot to set channels to 1");
}
if (info.state != 0x10) {
WARN_LOG(Log::ME, "Caller forgot to set state to 0x10");
}
sas_.isStreaming = info.fileDataEnd > info.bufferByte;
if (sas_.isStreaming) {
INFO_LOG(Log::ME, "SasAtrac stream mode");
} else {
INFO_LOG(Log::ME, "SasAtrac non-streaming mode");
}
}

int Atrac2::EnqueueForSas(u32 address, u32 ptr) {
SceAtracIdInfo &info = context_->info;
// Set the new buffer up to be adopted by the next call to Decode that needs more data.
// Note: Can't call this if the decoder isn't asking for another buffer to be queued.
if (info.secondBuffer != 0xFFFFFFFF) {
return SCE_SAS_ERROR_ATRAC3_ALREADY_QUEUED;
}

if (address == 0 && ptr == 0) {
WARN_LOG(Log::ME, "Caller tries to send us a zero buffer. Something went wrong.");
}

DEBUG_LOG(Log::ME, "EnqueueForSas: Second buffer updated to %08x, sz: %08x", address, ptr);
info.secondBuffer = address;
info.secondBufferByte = ptr;
return 0;
}

// Completely different streaming setup!
void Atrac2::DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) {
SceAtracIdInfo &info = context_->info;
*bytesWritten = 0;

// First frame handling. Not sure if accurate. Set up the initial buffer as the current streaming buffer.
// Also works for the non-streaming case.
if (info.buffer) {
// Adopt it then zero it.
sasBasePtr_ = info.buffer;
sasReadOffset_ = 0;
info.buffer = 0;
}
sas_.curBuffer = 0;
sas_.bufPtr[0] = info.buffer;
sas_.bufSize[0] = info.bufferByte - info.streamOff; // also equals info.streamDataByte
sas_.streamOffset = 0;
sas_.fileOffset = info.bufferByte; // Possibly should just set it to info.curFileOff
info.buffer = 0; // yes, this happens.
}

u8 assembly[1000];
// Keep decoding from the current buffer until it runs out.
if (sas_.streamOffset + info.sampleSize <= sas_.bufSize[sas_.curBuffer]) {
// Just decode.
const u8 *srcData = Memory::GetPointer(sas_.bufPtr[sas_.curBuffer] + sas_.streamOffset);
int bytesConsumed = 0;
bool decodeResult = decoder_->Decode(srcData, info.sampleSize, &bytesConsumed, 1, dstData, bytesWritten);
if (!decodeResult) {
ERROR_LOG(Log::ME, "SAS failed to decode regular packet");
}
sas_.streamOffset += bytesConsumed;
} else if (sas_.isStreaming) {
// TODO: Do we need special handling for the first buffer, since SetData will wrap around that packet? I think yes!
DEBUG_LOG(Log::ME, "Streaming atrac through sas, and hit the end of buffer %d", sas_.curBuffer);

// Compute the part sizes using the current size.
int part1Size = sas_.bufSize[sas_.curBuffer] - sas_.streamOffset;
int part2Size = info.sampleSize - part1Size;
_dbg_assert_(part1Size >= 0);
if (part1Size >= 0) {
// Grab the partial packet, before we switch over to the other buffer.
Memory::Memcpy(assembly, sas_.bufPtr[sas_.curBuffer] + sas_.streamOffset, part1Size);
}

const u8 *srcData = Memory::GetPointer(sasBasePtr_ + sasReadOffset_);
// Check if we hit the end.
if (sas_.fileOffset >= info.fileDataEnd) {
DEBUG_LOG(Log::ME, "Streaming and hit the file end.");
*bytesWritten = 0;
*finish = 1;
return;
}

int outSamples = 0;
int bytesConsumed = 0;
decoder_->Decode(srcData, info.sampleSize, &bytesConsumed, 1, dstData, bytesWritten);
// Check that a new buffer actually exists
if (info.secondBuffer == sas_.bufPtr[sas_.curBuffer]) {
ERROR_LOG(Log::ME, "Can't enqueue the same buffer twice in a row!");
*bytesWritten = 0;
*finish = 1;
return;
}

sasReadOffset_ += bytesConsumed;
if ((int)info.secondBuffer < 0) {
ERROR_LOG(Log::ME, "AtracSas streaming ran out of data, no secondbuffer pending");
*bytesWritten = 0;
*finish = 1;
return;
}

if (sasReadOffset_ + info.dataOff >= info.fileDataEnd) {
*finish = 1;
} else {
*finish = 0;
// Switch to the other buffer.
sas_.curBuffer ^= 1;

sas_.bufPtr[sas_.curBuffer] = info.secondBuffer;
sas_.bufSize[sas_.curBuffer] = info.secondBufferByte;
sas_.fileOffset += info.secondBufferByte;

sas_.streamOffset = part2Size;

// If we'll reach the end during this buffer, set second buffer to 0, signaling that we don't need more data.
if (sas_.fileOffset >= info.fileDataEnd) {
// We've reached the end.
info.secondBuffer = 0;
DEBUG_LOG(Log::ME, "%08x >= %08x: Reached the end.", sas_.fileOffset, info.fileDataEnd);
} else {
// Signal to the caller that we accept a new next buffer.
info.secondBuffer = 0xFFFFFFFF;
}

DEBUG_LOG(Log::ME, "Switching over to buffer %d, updating buffer to %08x, sz: %08x. %s", sas_.curBuffer, info.secondBuffer, info.secondBufferByte, info.secondBuffer == 0xFFFFFFFF ? "Signalling for more data." : "");

// Copy the second half (or if part1Size == 0, the whole packet) to the assembly buffer.
Memory::Memcpy(assembly + part1Size, sas_.bufPtr[sas_.curBuffer], part2Size);
// Decode the packet from the assembly, whether it's was assembled from two or one.
const u8 *srcData = assembly;
int bytesConsumed = 0;
bool decodeResult = decoder_->Decode(srcData, info.sampleSize, &bytesConsumed, 1, dstData, bytesWritten);
if (!decodeResult) {
ERROR_LOG(Log::ME, "SAS failed to decode assembled packet");
}
}
}
9 changes: 5 additions & 4 deletions Core/HLE/AtracCtx2.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ class Atrac2 : public AtracBase {
void GetStreamDataInfo(u32 *writePtr, u32 *writableBytes, u32 *readOffset) override;
int GetSecondBufferInfo(u32 *fileOffset, u32 *desiredSize) override;
int AddStreamData(u32 bytesToAdd) override;
u32 AddStreamDataSas(u32 bufPtr, u32 bytesToAdd) override;
int ResetPlayPosition(int sample, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf, bool *delay) override;
int GetResetBufferInfo(AtracResetBufferInfo *bufferInfo, int sample, bool *delay) override;
int SetData(const Track &track, u32 buffer, u32 readSize, u32 bufferSize, int outputChannels) override;
Expand All @@ -42,7 +41,11 @@ class Atrac2 : public AtracBase {

u32 DecodeData(u8 *outbuf, u32 outbufPtr, int *SamplesNum, int *finish, int *remains) override;
int DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData, int *bytesWritten) override;

void CheckForSas() override;
int EnqueueForSas(u32 address, u32 ptr) override;
void DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) override;
const AtracSasStreamState *StreamStateForSas() const { return context_->info.state == 0x10 ? &sas_ : nullptr; }

u32 GetNextSamples() override;

Expand All @@ -55,7 +58,6 @@ class Atrac2 : public AtracBase {
void NotifyGetContextAddress() override {}

int GetContextVersion() const override { return 2; }

u32 GetInternalCodecError() const override;

private:
Expand All @@ -73,6 +75,5 @@ class Atrac2 : public AtracBase {

// This is hidden state inside sceSas, really. Not visible in the context.
// But it doesn't really matter whether it's here or there.
u32 sasBasePtr_ = 0;
int sasReadOffset_ = 0;
AtracSasStreamState sas_;
};
25 changes: 25 additions & 0 deletions Core/HLE/ErrorCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -467,4 +467,29 @@ enum PSPErrorCode : u32 {
SCE_SSL_ERROR_ALREADY_INIT = 0x80435020,
SCE_SSL_ERROR_OUT_OF_MEMORY = 0x80435022,
SCE_SSL_ERROR_INVALID_PARAMETER = 0x804351FE,

SCE_SAS_ERROR_INVALID_GRAIN = 0x80420001,
SCE_SAS_ERROR_INVALID_MAX_VOICES = 0x80420002,
SCE_SAS_ERROR_INVALID_OUTPUT_MODE = 0x80420003,
SCE_SAS_ERROR_INVALID_SAMPLE_RATE = 0x80420004,
SCE_SAS_ERROR_BAD_ADDRESS = 0x80420005,
SCE_SAS_ERROR_INVALID_VOICE = 0x80420010,
SCE_SAS_ERROR_INVALID_NOISE_FREQ = 0x80420011,
SCE_SAS_ERROR_INVALID_PITCH = 0x80420012,
SCE_SAS_ERROR_INVALID_ADSR_CURVE_MODE = 0x80420013,
SCE_SAS_ERROR_INVALID_PARAMETER = 0x80420014,
SCE_SAS_ERROR_INVALID_LOOP_POS = 0x80420015,
SCE_SAS_ERROR_VOICE_PAUSED = 0x80420016,
SCE_SAS_ERROR_INVALID_VOLUME = 0x80420018,
SCE_SAS_ERROR_INVALID_ADSR_RATE = 0x80420019,
SCE_SAS_ERROR_INVALID_PCM_SIZE = 0x8042001A,
SCE_SAS_ERROR_REV_INVALID_TYPE = 0x80420020,
SCE_SAS_ERROR_REV_INVALID_FEEDBACK = 0x80420021,
SCE_SAS_ERROR_REV_INVALID_DELAY = 0x80420022,
SCE_SAS_ERROR_REV_INVALID_VOLUME = 0x80420023,
SCE_SAS_ERROR_BUSY = 0x80420030,
SCE_SAS_ERROR_ATRAC3_ALREADY_SET = 0x80420040,
SCE_SAS_ERROR_ATRAC3_NOT_SET = 0x80420041,
SCE_SAS_ERROR_ATRAC3_ALREADY_QUEUED = 0x80420042,
SCE_SAS_ERROR_NOT_INIT = 0x80420100,
};
Loading
Loading