From 1bd5cffa1d026fd216835bf7eb5688d95af2718f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 25 Mar 2025 16:45:50 +0100 Subject: [PATCH] Initial work on re-implementing atrac-through-sas --- Core/HLE/AtracCtx.cpp | 20 +----- Core/HLE/AtracCtx.h | 11 ++- Core/HLE/AtracCtx2.cpp | 128 +++++++++++++++++++++++++++-------- Core/HLE/AtracCtx2.h | 12 +++- Core/HLE/ErrorCodes.h | 1 + Core/HLE/sceAtrac.cpp | 4 +- Core/HLE/sceSas.cpp | 13 ++-- Core/HW/SasAudio.cpp | 14 ++-- Core/HW/SasAudio.h | 16 ++--- UI/ImDebugger/ImDebugger.cpp | 2 +- 10 files changed, 146 insertions(+), 75 deletions(-) diff --git a/Core/HLE/AtracCtx.cpp b/Core/HLE/AtracCtx.cpp index 4339628b24..bc3eff908b 100644 --- a/Core/HLE/AtracCtx.cpp +++ b/Core/HLE/AtracCtx.cpp @@ -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; @@ -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() { diff --git a/Core/HLE/AtracCtx.h b/Core/HLE/AtracCtx.h index 5d80daa909..3696ac7dac 100644 --- a/Core/HLE/AtracCtx.h +++ b/Core/HLE/AtracCtx.h @@ -112,7 +112,6 @@ public: 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; @@ -121,10 +120,14 @@ public: 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 ¶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 int GetSoundSample(int *endSample, int *loopStartSample, int *loopEndSample) const = 0; virtual int GetContextVersion() const = 0; @@ -205,7 +208,6 @@ public: 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; @@ -213,6 +215,9 @@ public: 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. diff --git a/Core/HLE/AtracCtx2.cpp b/Core/HLE/AtracCtx2.cpp index a3aeec8d36..7dbac9550b 100644 --- a/Core/HLE/AtracCtx2.cpp +++ b/Core/HLE/AtracCtx2.cpp @@ -175,15 +175,16 @@ 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; @@ -191,10 +192,17 @@ void Atrac2::DoState(PointerWrap &p) { // 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; @@ -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) { @@ -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)) { @@ -1039,27 +1037,97 @@ 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"); + } + sasIsStreaming_ = info.fileDataEnd >= info.bufferByte; +} + +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; + } + + info.secondBuffer = address; + info.secondBufferByte = ptr; + return 0; +} + void Atrac2::DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) { SceAtracIdInfo &info = context_->info; - if (info.buffer) { - // Adopt it then zero it. - sasBasePtr_ = info.buffer; - sasReadOffset_ = 0; - info.buffer = 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 (sasBufferPtr_[0] == 0 && sasCurBuffer_ == 0) { + sasBufferPtr_[0] = info.buffer; + sasBufferSize_[0] = info.bufferByte - info.streamOff; + sasStreamOffset_ = 0; + sasFileOffset_ = 0; } - const u8 *srcData = Memory::GetPointer(sasBasePtr_ + sasReadOffset_); + 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 outSamples = 0; - int bytesConsumed = 0; - decoder_->Decode(srcData, info.sampleSize, &bytesConsumed, 1, dstData, bytesWritten); + 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; + } - sasReadOffset_ += bytesConsumed; + // 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; - } + }*/ } diff --git a/Core/HLE/AtracCtx2.h b/Core/HLE/AtracCtx2.h index 0e9ce26add..612c182a92 100644 --- a/Core/HLE/AtracCtx2.h +++ b/Core/HLE/AtracCtx2.h @@ -33,7 +33,6 @@ public: 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; @@ -42,6 +41,9 @@ public: 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; @@ -73,6 +75,10 @@ private: // 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; }; diff --git a/Core/HLE/ErrorCodes.h b/Core/HLE/ErrorCodes.h index 15f240195d..62a629d087 100644 --- a/Core/HLE/ErrorCodes.h +++ b/Core/HLE/ErrorCodes.h @@ -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, }; diff --git a/Core/HLE/sceAtrac.cpp b/Core/HLE/sceAtrac.cpp index 826d1816a3..87a7af42e2 100644 --- a/Core/HLE/sceAtrac.cpp +++ b/Core/HLE/sceAtrac.cpp @@ -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) { @@ -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; } diff --git a/Core/HLE/sceSas.cpp b/Core/HLE/sceSas.cpp index a3b8b9af77..f9dc374bca 100644 --- a/Core/HLE/sceSas.cpp +++ b/Core/HLE/sceSas.cpp @@ -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); } @@ -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) { @@ -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"); @@ -726,8 +730,7 @@ void __SasGetDebugStats(char *stats, size_t bufsize) { } } -const HLEFunction sceSasCore[] = -{ +const HLEFunction sceSasCore[] = { {0X42778A9F, &WrapU_UUUUU, "__sceSasInit", 'x', "xxxxx" }, {0XA3589D81, &WrapU_UU<_sceSasCore>, "__sceSasCore", 'x', "xx" }, {0X50A14DFC, &WrapU_UUII<_sceSasCoreWithMix>, "__sceSasCoreWithMix", 'x', "xxii" }, diff --git a/Core/HW/SasAudio.cpp b/Core/HW/SasAudio.cpp index e36919d1c3..42c86444dc 100644 --- a/Core/HW/SasAudio.cpp +++ b/Core/HW/SasAudio.cpp @@ -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; } @@ -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)); diff --git a/Core/HW/SasAudio.h b/Core/HW/SasAudio.h index 6627c33129..fb480a0ceb 100644 --- a/Core/HW/SasAudio.h +++ b/Core/HW/SasAudio.h @@ -137,11 +137,10 @@ private: class SasAtrac3 { public: - SasAtrac3() : contextAddr_(0), atracID_(-1), sampleQueue_(0), end_(false) {} - ~SasAtrac3() { delete sampleQueue_; } + ~SasAtrac3() { delete sampleQueue_; delete[] buf_; } + int AtracID() const { return atracID_; } // for the debugger int SetContext(u32 context); - int id() const { return atracID_; } - void getNextSamples(s16 *outbuf, int wantedSamples); + void GetNextSamples(s16 *outbuf, int wantedSamples); int Concatenate(u32 bufPtr, u32 addbytes); void DoState(PointerWrap &p); bool End() const { @@ -149,10 +148,11 @@ public: } private: - u32 contextAddr_; - int atracID_; - BufferQueue *sampleQueue_; - bool end_; + u32 contextAddr_ = 0; + int atracID_ = -1; + BufferQueue *sampleQueue_ = nullptr; + bool end_ = false; + s16 *buf_ = nullptr; }; class ADSREnvelope { diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index e70dbd88ea..40691bea33 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -1306,7 +1306,7 @@ void DrawSasAudio(ImConfig &cfg) { case VOICETYPE_OFF: ImGui::TextUnformatted("(off)"); break; case VOICETYPE_VAG: ImGui::Text("%08x", voice.vagAddr); break; case VOICETYPE_PCM: ImGui::Text("%08x", voice.pcmAddr); break; - case VOICETYPE_ATRAC3: ImGui::Text("atrac: %d", voice.atrac3.id()); break; + case VOICETYPE_ATRAC3: ImGui::Text("atrac: %d", voice.atrac3.AtracID()); break; default: ImGui::TextUnformatted("N/A"); break;