Skip to content
Prev Previous commit
Next Next commit
Initial work on re-implementing atrac-through-sas
  • Loading branch information
hrydgard committed Mar 25, 2025
commit 1bd5cffa1d026fd216835bf7eb5688d95af2718f
20 changes: 2 additions & 18 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 @@ -962,9 +947,8 @@ int Atrac::DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData, i
}

void Atrac::DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) {
// Hack, but works.
int samplesNum;
DecodeData((u8 *)dstData, 0, &samplesNum, finish, nullptr);
// Disabled, can't work now that we changed the interface.
*finish = 1;
}

void Atrac::NotifyGetContextAddress() {
Expand Down
11 changes: 8 additions & 3 deletions Core/HLE/AtracCtx.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,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 +120,14 @@ 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 int GetSoundSample(int *endSample, int *loopStartSample, int *loopEndSample) const = 0;

virtual int GetContextVersion() const = 0;
Expand Down Expand Up @@ -205,14 +208,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() {}
int EnqueueForSas(u32 address, u32 ptr) override { return 0; }
void DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) override;

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

sasReadOffset_ = 0;
sasBasePtr_ = 0;
sasStreamOffset_ = 0;
sasBufferPtr_[0] = 0;
sasBufferPtr_[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, sasStreamOffset_);
Do(p, sasBufferPtr_[0]);
}
// Added support for streaming sas audio, need some more context state.
if (s >= 3) {
Do(p, sasBufferPtr_[1]);
Do(p, sasBufferSize_);
Do(p, sasIsStreaming_);
}

const SceAtracIdInfo &info = context_->info;
Expand Down Expand Up @@ -497,20 +505,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 +675,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 +1037,97 @@ int Atrac2::DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData,
return 0;
}

void Atrac2::DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) {
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");
}
sasIsStreaming_ = info.fileDataEnd >= info.bufferByte;
}

if (info.buffer) {
// Adopt it then zero it.
sasBasePtr_ = info.buffer;
sasReadOffset_ = 0;
info.buffer = 0;
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;
}

const u8 *srcData = Memory::GetPointer(sasBasePtr_ + sasReadOffset_);
info.secondBuffer = address;
info.secondBufferByte = ptr;
return 0;
}

int outSamples = 0;
int bytesConsumed = 0;
decoder_->Decode(srcData, info.sampleSize, &bytesConsumed, 1, dstData, bytesWritten);
void Atrac2::DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) {
SceAtracIdInfo &info = context_->info;

sasReadOffset_ += bytesConsumed;
// 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 (sasBufferPtr_[0] == 0 && sasCurBuffer_ == 0) {
sasBufferPtr_[0] = info.buffer;
sasBufferSize_[0] = info.bufferByte - info.streamOff;
sasStreamOffset_ = 0;
sasFileOffset_ = 0;
}

u8 assembly[1000];
// Keep decoding from the current buffer until it runs out.
if (sasStreamOffset_ + info.sampleSize <= sasBufferSize_[sasCurBuffer_]) {
// Just decode.
const u8 *srcData = Memory::GetPointer(sasBufferPtr_[sasCurBuffer_] + sasStreamOffset_);
int bytesConsumed = 0;
bool decodeResult = decoder_->Decode(srcData, info.sampleSize, &bytesConsumed, 1, dstData, bytesWritten);
if (!decodeResult) {
ERROR_LOG(Log::ME, "SAS failed to decode packet");
}
sasStreamOffset_ += bytesConsumed;
sasFileOffset_ += bytesConsumed;
} else if (sasIsStreaming_) {
// TODO: Do we need special handling for the first buffer, since SetData will wrap around that packet? I think yes!

WARN_LOG(Log::ME, "Streaming, and hit the end of buffer %d, switching over.", sasCurBuffer_);

int part1Size = sasBufferSize_[sasCurBuffer_] - sasStreamOffset_;
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, sasBufferPtr_[sasCurBuffer_] + sasStreamOffset_, part1Size);
}
// Check that a new buffer actually exists
if ((int)info.secondBuffer < 0 || info.secondBuffer == sasBufferPtr_[sasCurBuffer_]) {
ERROR_LOG(Log::ME, "AtracSas streaming ran out of data, no secondbuffer pending");
*finish = 1;
return;
}

// Switch to the other buffer.
sasCurBuffer_ ^= 1;

sasBufferPtr_[sasCurBuffer_] = info.secondBuffer;
sasBufferSize_[sasCurBuffer_] = info.secondBufferByte;
sasStreamOffset_ = part2Size;
info.secondBuffer = 0xFFFFFFFF; // Signal to the caller that we accept a new next buffer. NOTE: This

// Copy the second half (or if part1Size == 0, the whole packet) to the assembly buffer.
Memory::Memcpy(assembly + part1Size, sasBufferPtr_[sasCurBuffer_], 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);
sasFileOffset_ += bytesConsumed;
if (!decodeResult) {
ERROR_LOG(Log::ME, "SAS failed to decode packet");
}
}

/*
if (sasReadOffset_ + info.dataOff >= info.fileDataEnd) {
*finish = 1;
} else {
*finish = 0;
}
}*/
}
12 changes: 9 additions & 3 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,6 +41,9 @@ 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;

u32 GetNextSamples() override;
Expand Down Expand Up @@ -73,6 +75,10 @@ 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;
u32 sasBufferPtr_[2]{};
u32 sasBufferSize_[2]{};
int sasStreamOffset_ = 0;
int sasFileOffset_ = 0;
int sasCurBuffer_ = 0;
bool sasIsStreaming_ = false;
};
1 change: 1 addition & 0 deletions Core/HLE/ErrorCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -490,5 +490,6 @@ enum PSPErrorCode : u32 {
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,
};
4 changes: 2 additions & 2 deletions Core/HLE/sceAtrac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@ u32 AtracSasAddStreamData(int atracID, u32 bufPtr, u32 bytesToAdd) {
if (!atrac) {
WARN_LOG(Log::ME, "bad atrac ID");
}
return atrac->AddStreamDataSas(bufPtr, bytesToAdd);
return atrac->EnqueueForSas(bufPtr, bytesToAdd);
}

void AtracSasDecodeData(int atracID, u8* outbuf, int *SamplesNum, int *finish) {
Expand Down Expand Up @@ -1140,7 +1140,7 @@ int AtracSasBindContextAndGetID(u32 contextAddr) {

// Not actually a hack, this happens.
AtracBase *atrac = getAtrac(atracID);
atrac->SetOutputChannels(1);
atrac->CheckForSas();
return atracID;
}

Expand Down
13 changes: 8 additions & 5 deletions Core/HLE/sceSas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,6 @@ static u32 __sceSasSetVoiceATRAC3(u32 core, int voiceNum, u32 atrac3Context) {
v.playing = true;
v.atrac3.SetContext(atrac3Context);
Memory::Write_U32(atrac3Context, core + 56 * voiceNum + 20);

return hleLogDebug(Log::sceSas, 0);
}

Expand All @@ -691,11 +690,15 @@ static u32 __sceSasConcatenateATRAC3(u32 core, int voiceNum, u32 atrac3DataAddr,
}

DEBUG_LOG_REPORT_ONCE(concatAtrac3, Log::sceSas, "__sceSasConcatenateATRAC3(%08x, %i, %08x, %i)", core, voiceNum, atrac3DataAddr, atrac3DataLength);

__SasDrain();

SasVoice &v = sas->voices[voiceNum];
if (Memory::IsValidAddress(atrac3DataAddr))
if (Memory::IsValidAddress(atrac3DataAddr)) {
v.atrac3.Concatenate(atrac3DataAddr, atrac3DataLength);
return hleNoLog(0);
}

return hleLogDebug(Log::sceSas, 0);
}

static u32 __sceSasUnsetATRAC3(u32 core, int voiceNum) {
Expand All @@ -704,6 +707,7 @@ static u32 __sceSasUnsetATRAC3(u32 core, int voiceNum) {
}

__SasDrain();

SasVoice &v = sas->voices[voiceNum];
if (v.type != VOICETYPE_ATRAC3) {
return hleLogError(Log::sceSas, SCE_SAS_ERROR_ATRAC3_NOT_SET, "voice is not ATRAC3");
Expand All @@ -726,8 +730,7 @@ void __SasGetDebugStats(char *stats, size_t bufsize) {
}
}

const HLEFunction sceSasCore[] =
{
const HLEFunction sceSasCore[] = {
{0X42778A9F, &WrapU_UUUUU<sceSasInit>, "__sceSasInit", 'x', "xxxxx" },
{0XA3589D81, &WrapU_UU<_sceSasCore>, "__sceSasCore", 'x', "xx" },
{0X50A14DFC, &WrapU_UUII<_sceSasCoreWithMix>, "__sceSasCoreWithMix", 'x', "xxii" },
Expand Down
14 changes: 9 additions & 5 deletions Core/HW/SasAudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,19 +200,23 @@ int SasAtrac3::SetContext(u32 contextAddr) {
return 0;
}

void SasAtrac3::getNextSamples(s16 *outbuf, int wantedSamples) {
void SasAtrac3::GetNextSamples(s16 *outbuf, int wantedSamples) {
if (atracID_ < 0) {
end_ = true;
return;
}

if (!buf_) {
buf_ = new s16[0x800];
}

int finish = 0;
int wantedbytes = wantedSamples * sizeof(s16);
while (!finish && sampleQueue_->getQueueSize() < wantedbytes) {
int numSamples = 0;
static s16 buf[0x800];
AtracSasDecodeData(atracID_, (u8*)buf, &numSamples, &finish);
AtracSasDecodeData(atracID_, (u8*)buf_, &numSamples, &finish);
if (numSamples > 0)
sampleQueue_->push((u8*)buf, numSamples * sizeof(s16));
sampleQueue_->push((u8*)buf_, numSamples * sizeof(s16));
else
finish = 1;
}
Expand Down Expand Up @@ -497,7 +501,7 @@ void SasVoice::ReadSamples(s16 *output, int numSamples) {
}
break;
case VOICETYPE_ATRAC3:
atrac3.getNextSamples(output, numSamples);
atrac3.GetNextSamples(output, numSamples);
break;
default:
memset(output, 0, numSamples * sizeof(s16));
Expand Down
Loading