#include #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" #include "Common/Log.h" #include "Core/MemMapHelpers.h" #include "Core/HLE/HLE.h" #include "Core/HLE/ErrorCodes.h" #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" #include "Common/File/FileUtil.h" // Convenient command line: // Windows\x64\debug\PPSSPPHeadless.exe --root pspautotests/tests/../ -o --compare --new-atrac --timeout=30 --graphics=software pspautotests/tests/audio/atrac/stream.prx // // See the big comment in sceAtrac.cpp for an overview of the different modes of operation. // To run on the real PSP, without gentest.py: // cmd1> C:\dev\ppsspp\pspautotests\tests\audio\atrac> make // cmd2> C:\dev\ppsspp\pspautotests> usbhostfs_pc -b 4000 // cmd3> C:\dev\ppsspp\pspautotests> pspsh -p 4000 // cmd3> > cd host0:/test/audio/atrac // cmd3> stream.prx // cmd1> C:\dev\ppsspp\pspautotests\tests\audio\atrac>copy /Y ..\..\..\__testoutput.txt stream.expected // Then run the test, see above. 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) { return size - (size % grain); } static int RoundDownToMultipleWithOffset(int offset, int size, int grain) { if (size > offset) { return ((size - offset) / grain) * grain + offset; } else { return size; } } static int ComputeSkipFrames(const SceAtracIdInfo &info, int seekPos) { // No idea why this is the rule, but this is the rule. return (seekPos & info.SamplesFrameMask()) < info.SkipSamples() ? 2 : 1; } static int ComputeFileOffset(const SceAtracIdInfo &info, int seekPos) { int frameOffset = ((seekPos / info.SamplesPerFrame()) - 1) * info.sampleSize; if ((seekPos & info.SamplesFrameMask()) < info.SkipSamples() && (frameOffset != 0)) { frameOffset -= info.sampleSize; } return frameOffset + info.dataOff; } // Unlike the above, this one need to be inclusive. static int ComputeLoopEndFileOffset(const SceAtracIdInfo &info, int seekPos) { return (seekPos / info.SamplesPerFrame() + 1) * info.sampleSize + info.dataOff; } static int ComputeSpaceUsed(const SceAtracIdInfo &info) { // The odd case: If streaming from the second buffer, and we're past the loop end (we're in the tail)... if (info.decodePos > info.loopEnd && info.curBuffer == 1) { int space = info.secondBufferByte; if (info.secondStreamOff < space) { space = RoundDownToMultipleWithOffset(info.secondStreamOff, info.secondBufferByte, info.sampleSize); } if ((info.secondStreamOff <= space) && (space - info.secondStreamOff < info.streamDataByte)) { return info.streamDataByte - (space - info.secondStreamOff); } return 0; } // The normal case. return info.streamDataByte; } static int ComputeRemainFrameStream(const SceAtracIdInfo &info) { if (info.streamDataByte >= info.fileDataEnd - info.curFileOff) { // Already done. return PSP_ATRAC_NONLOOP_STREAM_DATA_IS_ON_MEMORY; } // Since we're streaming, the remaining frames are what's valid in the buffer. return std::max(0, info.streamDataByte / info.sampleSize - (int)info.numSkipFrames); } // This got so complicated! static int ComputeRemainFrameLooped(const SceAtracIdInfo &info) { const int loopStartFileOffset = ComputeFileOffset(info, info.loopStart); const int loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd); const int writeFileOff = info.curFileOff + info.streamDataByte; const int leftToRead = writeFileOff - loopEndFileOffset; int remainFrames; if (writeFileOff <= loopEndFileOffset) { // Simple case - just divide to find the number of frames remaining in the buffer. remainFrames = info.streamDataByte / info.sampleSize; } else { // Darn, we need to take looping into account... const int skipFramesAtLoopStart = ComputeSkipFrames(info, info.loopStart); const int firstPartLength = loopEndFileOffset - loopStartFileOffset; const int secondPartLength = leftToRead % firstPartLength; // Sum up all the parts (the buffered, the space remaining before the loop point // and the space after the loop point), each divided by sample size, need to take skipped frames into account. remainFrames = (loopEndFileOffset - info.curFileOff) / info.sampleSize + (leftToRead / firstPartLength) * (firstPartLength / info.sampleSize - skipFramesAtLoopStart); if (secondPartLength > skipFramesAtLoopStart * info.sampleSize) { remainFrames += secondPartLength / info.sampleSize - skipFramesAtLoopStart; } } // Clamp to zero. remainFrames = std::max(0, remainFrames - (int)info.numSkipFrames); if (info.loopNum < 0) { // Infinite looping while streaming, we never return that we're done reading data. return remainFrames; } // Additional check for distance to end of playback if we're looping a finite amount of times. const int streamBufferEndFileOffset = info.curFileOff + info.streamDataByte; if (streamBufferEndFileOffset >= loopEndFileOffset) { const int numBufferedLoops = (streamBufferEndFileOffset - loopEndFileOffset) / (loopEndFileOffset - loopStartFileOffset); if (info.loopNum <= numBufferedLoops) { return PSP_ATRAC_LOOP_STREAM_DATA_IS_ON_MEMORY; } } 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 * 2) { return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; } 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; // Handle the easy cases first. switch (info.state) { case ATRAC_STATUS_UNINITIALIZED: case ATRAC_STATUS_NO_DATA: return 0; case ATRAC_STATUS_ALL_DATA_LOADED: return PSP_ATRAC_ALLDATA_IS_ON_MEMORY; // Not sure about no data. case ATRAC_STATUS_HALFWAY_BUFFER: { // Pretty simple - compute the remaining space, and divide by the sample size, adjusting for frames-to-skip. const int writeFileOff = info.dataOff + info.streamDataByte; if (info.curFileOff < writeFileOff) { return std::max(0, (writeFileOff - info.curFileOff) / info.sampleSize - (int)info.numSkipFrames); } return 0; } case ATRAC_STATUS_STREAMED_WITHOUT_LOOP: return ComputeRemainFrameStream(info); case ATRAC_STATUS_STREAMED_LOOP_FROM_END: return ComputeRemainFrameLooped(info); case ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER: if (info.decodePos <= info.loopEnd) { // If before the tail, just treat it as looped. return ComputeRemainFrameLooped(info); } else { // If in tail, treat is as unlooped. return ComputeRemainFrameStream(info); } break; default: return SCE_ERROR_ATRAC_BAD_ATRACID; } } Atrac2::Atrac2(u32 contextAddr, int codecType) { if (contextAddr) { context_ = PSPPointer::Create(contextAddr); // First-time initialization. The rest is initialized in SetData. SceAtracIdInfo &info = context_->info; info.codec = codecType; info.state = ATRAC_STATUS_NO_DATA; info.curBuffer = 0; sas_.streamOffset = 0; sas_.bufPtr[0] = 0; sas_.bufPtr[1] = 0; } else { // We're loading state, we'll restore the context in DoState. } } Atrac2::~Atrac2() { DumpBufferToFile(); delete[] decodeTemp_; // Nothing else to do here, the context is freed by the HLE. } void Atrac2::DumpBufferToFile() { if (dumped_) { // Already dumped, no need to dump again. return; } if (!dumpBuffer_.empty()) { std::string filename = StringFromFormat("atrac3_%08x_incomplete.at3", context_->info.endSample); DumpFileIfEnabled(dumpBuffer_.data(), (u32)dumpBuffer_.size(), filename, DumpFileType::Atrac3); dumpBuffer_.clear(); } dumped_ = true; } void Atrac2::DoState(PointerWrap &p) { 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, 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; if (p.mode == p.MODE_READ && info.state != ATRAC_STATUS_NO_DATA) { CreateDecoder(info.codec, info.sampleSize, info.numChan); } } bool Atrac2::HasSecondBuffer() const { const SceAtracIdInfo &info = context_->info; return info.secondBufferByte != 0; } int Atrac2::GetSoundSample(int *outEndSample, int *outLoopStartSample, int *outLoopEndSample) const { const SceAtracIdInfo &info = context_->info; *outEndSample = info.endSample - info.firstValidSample; int loopEnd = -1; if (info.loopEnd == 0) { *outLoopStartSample = -1; *outLoopEndSample = -1; } else { *outLoopStartSample = info.loopStart - info.firstValidSample; *outLoopEndSample = info.loopEnd - info.firstValidSample; } return 0; } int Atrac2::ResetPlayPosition(int seekPos, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf, bool *delay) { *delay = false; // This was mostly copied straight from the old impl. SceAtracIdInfo &info = context_->info; if (info.state == ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER && info.secondBufferByte == 0) { return SCE_ERROR_ATRAC_SECOND_BUFFER_NEEDED; } seekPos += info.firstValidSample; if ((u32)seekPos > (u32)info.endSample) { return SCE_ERROR_ATRAC_BAD_SAMPLE; } int result = ResetPlayPositionInternal(seekPos, bytesWrittenFirstBuf, bytesWrittenSecondBuf); if (result >= 0) { int skipCount = 0; result = SkipFrames(&skipCount); if (skipCount) { *delay = true; } } return result; } u32 Atrac2::ResetPlayPositionInternal(int seekPos, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf) { // Redo the same calculation as before, for input validation. AtracResetBufferInfo bufferInfo; GetResetBufferInfoInternal(&bufferInfo, seekPos); // Input validation. if ((u32)bytesWrittenFirstBuf < bufferInfo.first.minWriteBytes || (u32)bytesWrittenFirstBuf > bufferInfo.first.writableBytes) { return SCE_ERROR_ATRAC_BAD_FIRST_RESET_SIZE; } if ((u32)bytesWrittenSecondBuf < bufferInfo.second.minWriteBytes || (u32)bytesWrittenSecondBuf > bufferInfo.second.writableBytes) { return SCE_ERROR_ATRAC_BAD_SECOND_RESET_SIZE; } SceAtracIdInfo &info = context_->info; info.decodePos = seekPos; info.numSkipFrames = ComputeSkipFrames(info, seekPos); info.loopNum = 0; info.curFileOff = ComputeFileOffset(info, seekPos); context_->codec.err = 0x20b; // wtf? testing shows it. switch (info.state) { case ATRAC_STATUS_ALL_DATA_LOADED: // We're done. return 0; case ATRAC_STATUS_HALFWAY_BUFFER: info.streamDataByte += bytesWrittenFirstBuf; if (info.dataOff + info.streamDataByte >= info.fileDataEnd) { // Buffer full, we can transition to a full buffer here, if all the bytes were written. Let's do it. info.state = ATRAC_STATUS_ALL_DATA_LOADED; } return 0; case ATRAC_STATUS_STREAMED_WITHOUT_LOOP: case ATRAC_STATUS_STREAMED_LOOP_FROM_END: // We just adopt the bytes that were written as our stream data, no math needed. info.streamDataByte = bytesWrittenFirstBuf; info.curBuffer = 0; info.streamOff = 0; return 0; case ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER: { // As usual with the second buffer and trailer, things get tricky here. const int loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd); if (info.curFileOff >= loopEndFileOffset) { const int secondBufferSizeRounded = RoundDownToMultiple(info.secondBufferByte, info.sampleSize); if (info.curFileOff < loopEndFileOffset + secondBufferSizeRounded) { info.streamDataByte = ((loopEndFileOffset + secondBufferSizeRounded) - info.curFileOff) + bytesWrittenFirstBuf; info.curBuffer = 1; info.secondStreamOff = info.curFileOff - loopEndFileOffset; } else { info.streamDataByte = bytesWrittenFirstBuf; info.curBuffer = 2; // Temporary value! Will immediately switch back to 0. info.streamOff = 0; } } else { info.streamDataByte = bytesWrittenFirstBuf; info.curBuffer = 0; info.streamOff = 0; } return 0; } default: _dbg_assert_(false); return 0; } } // This is basically sceAtracGetBufferInfoForResetting. // NOTE: Not const! This can cause SkipFrames! int Atrac2::GetBufferInfoForResetting(AtracResetBufferInfo *bufferInfo, int seekPos, bool *delay) { const SceAtracIdInfo &info = context_->info; if (info.state == ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER && info.secondBufferByte == 0) { return SCE_ERROR_ATRAC_SECOND_BUFFER_NEEDED; } seekPos += info.firstValidSample; if ((u32)seekPos > (u32)info.endSample) { return SCE_ERROR_ATRAC_BAD_SAMPLE; } GetResetBufferInfoInternal(bufferInfo, seekPos); // Yes, this happens here! If there are any frames to skip, they get skipped! // Even though this looks like a function that shouldn't change the state. int skipCount = 0; int retval = SkipFrames(&skipCount); if (skipCount > 0) *delay = true; return retval; } void Atrac2::GetResetBufferInfoInternal(AtracResetBufferInfo *bufferInfo, int seekPos) const { const SceAtracIdInfo &info = context_->info; switch (info.state) { case ATRAC_STATUS_NO_DATA: case ATRAC_STATUS_ALL_DATA_LOADED: // Everything is loaded, so nothing needs to be read. bufferInfo->first.writePosPtr = info.buffer; bufferInfo->first.writableBytes = 0; bufferInfo->first.minWriteBytes = 0; bufferInfo->first.filePos = 0; break; case ATRAC_STATUS_HALFWAY_BUFFER: { // Not too hard, we just ask to fill up the missing part of the buffer. const int streamPos = info.dataOff + info.streamDataByte; const int fileOff = info.dataOff + (seekPos / info.SamplesPerFrame() + 1) * info.sampleSize; bufferInfo->first.writePosPtr = info.buffer + streamPos; bufferInfo->first.writableBytes = info.fileDataEnd - streamPos; bufferInfo->first.filePos = streamPos; bufferInfo->first.minWriteBytes = std::max(0, fileOff - streamPos); break; } case ATRAC_STATUS_STREAMED_WITHOUT_LOOP: case ATRAC_STATUS_STREAMED_LOOP_FROM_END: { // Relatively easy, just can't forget those skipped frames. const int curFileOffset = ComputeFileOffset(info, seekPos); const int bufferEnd = RoundDownToMultiple(info.bufferByte, info.sampleSize); bufferInfo->first.writePosPtr = info.buffer; bufferInfo->first.writableBytes = std::min(info.fileDataEnd - curFileOffset, bufferEnd); bufferInfo->first.minWriteBytes = (ComputeSkipFrames(info, seekPos) + 1) * info.sampleSize; bufferInfo->first.filePos = curFileOffset; break; } case ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER: { // As usual, with the second buffer, things get crazy complicated... const int seekFileOffset = ComputeFileOffset(info, seekPos); const int loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd) - 1; const int bufferEnd = RoundDownToMultiple(info.bufferByte, info.sampleSize); const int skipBytes = (ComputeSkipFrames(info, seekPos) + 1) * info.sampleSize; const int secondBufferEnd = RoundDownToMultiple(info.secondBufferByte, info.sampleSize); if (seekFileOffset < loopEndFileOffset) { const int remainingBeforeLoop = (loopEndFileOffset - seekFileOffset) + 1; bufferInfo->first.writePosPtr = info.buffer; bufferInfo->first.writableBytes = std::min(bufferEnd, remainingBeforeLoop); bufferInfo->first.minWriteBytes = std::min(skipBytes, remainingBeforeLoop); bufferInfo->first.filePos = seekFileOffset; } else if (loopEndFileOffset + secondBufferEnd <= seekFileOffset) { bufferInfo->first.writePosPtr = info.buffer; bufferInfo->first.writableBytes = std::min(info.fileDataEnd - seekFileOffset, bufferEnd); bufferInfo->first.minWriteBytes = skipBytes; bufferInfo->first.filePos = seekFileOffset; } else if (loopEndFileOffset + (int)info.secondBufferByte + 1 < info.fileDataEnd) { const int endOffset = loopEndFileOffset + secondBufferEnd + 1; bufferInfo->first.writePosPtr = info.buffer; bufferInfo->first.writableBytes = std::min(info.fileDataEnd - endOffset, bufferEnd); bufferInfo->first.minWriteBytes = std::max(0, seekFileOffset + skipBytes - endOffset); bufferInfo->first.filePos = endOffset; } else { bufferInfo->first.writePosPtr = info.buffer; bufferInfo->first.writableBytes = 0; bufferInfo->first.minWriteBytes = 0; bufferInfo->first.filePos = 0; } break; } default: _dbg_assert_(false); break; } // Reset never needs a second buffer write, since the loop is in a fixed place. // It seems like second.writePosPtr is always the same as the first buffer's pos, weirdly (doesn't make sense). bufferInfo->second.writePosPtr = info.buffer; bufferInfo->second.writableBytes = 0; bufferInfo->second.minWriteBytes = 0; bufferInfo->second.filePos = 0; } int Atrac2::SetLoopNum(int loopNum) { SceAtracIdInfo &info = context_->info; if (info.loopEnd <= 0) { // File doesn't contain loop information, looping isn't allowed. return SCE_ERROR_ATRAC_NO_LOOP_INFORMATION; } // Just override the current loop counter. info.loopNum = loopNum; return 0; } int Atrac2::LoopNum() const { return context_->info.loopNum; } int Atrac2::LoopStatus() const { const SceAtracIdInfo &info = context_->info; if (info.loopEnd == 0) { // No loop available. return 0; } else if (info.loopNum != 0) { // We've got at least one loop to go. return 1; } else { // Return 1 if we haven't passed the loop point. return info.decodePos <= info.loopEnd ? 1 : 0; } } u32 Atrac2::GetNextSamples() { SceAtracIdInfo &info = context_->info; // TODO: Need to reformulate this. const int endOfCurrentFrame = info.decodePos | info.SamplesFrameMask(); // bit trick! const int remainder = std::max(0, endOfCurrentFrame - info.endSample); const int adjusted = (info.decodePos & info.SamplesFrameMask()) + remainder; return std::max(0, info.SamplesPerFrame() - adjusted); } int Atrac2::GetNextDecodePosition(int *pos) const { const SceAtracIdInfo &info = context_->info; // Check if we reached the end. if (info.decodePos > info.endSample) { return SCE_ERROR_ATRAC_ALL_DATA_DECODED; } // Check if remaining data in the file is smaller than a frame. if (info.fileDataEnd - info.curFileOff < info.sampleSize) { return SCE_ERROR_ATRAC_ALL_DATA_DECODED; } *pos = info.decodePos - info.firstValidSample; return 0; } int Atrac2::AddStreamData(u32 bytesToAdd) { SceAtracIdInfo &info = context_->info; // WARNING: bytesToAdd might not be sampleSize aligned, even though we return a sampleSize-aligned size // in GetStreamDataInfo, so other parts of the code still has to handle unaligned data amounts. if (info.state == ATRAC_STATUS_HALFWAY_BUFFER) { const int newFileOffset = info.streamDataByte + info.dataOff + bytesToAdd; if (newFileOffset == info.fileDataEnd) { info.state = ATRAC_STATUS_ALL_DATA_LOADED; } else if (newFileOffset > info.fileDataEnd) { return SCE_ERROR_ATRAC_ADD_DATA_IS_TOO_BIG; } info.streamDataByte += bytesToAdd; } else { // TODO: Check for SCE_ERROR_ATRAC_ADD_DATA_IS_TOO_BIG in the other modes too. 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) { const int loopLength = loopEndFileOffset - loopStartFileOffset; return loopLength - ((info.curFileOff + info.streamDataByte) - loopEndFileOffset) % loopLength; } else { return loopEndFileOffset - writeOffset; } } static int IncrementAndLoop(int curOffset, int increment, int loopStart, int loopEnd) { const int sum = curOffset + increment; if (sum >= loopEnd) { return loopStart + (sum - loopEnd) % (loopEnd - loopStart); } else { return sum; } } static int WrapAroundRoundedBufferSize(int offset, int bufferSize, int addend, int grainSize) { bufferSize = RoundDownToMultipleWithOffset(offset, bufferSize, grainSize); const int sum = offset + addend; if (bufferSize <= sum) { return sum - bufferSize; } else { return sum; } } static void ComputeStreamBufferDataInfo(const SceAtracIdInfo &info, u32 *writePtr, u32 *bytesToWrite, u32 *readFileOffset) { // Streaming data info // // This really is the core logic of sceAtrac. Initially I had it quite complicated, but boiled it // all down to fairly simple logic. And then boiled it down further and fixed bugs. // And then had to make it WAY complicated again to support looping... Sigh. const u32 streamOff = info.curBuffer != 1 ? info.streamOff : 0; const int spaceUsed = ComputeSpaceUsed(info); const int spaceLeftAfterStreamOff = RoundDownToMultipleWithOffset(streamOff, info.bufferByte, info.sampleSize); const int streamPos = streamOff + spaceUsed; int spaceLeftInBuffer; if (streamPos >= spaceLeftAfterStreamOff) { spaceLeftInBuffer = spaceLeftAfterStreamOff - spaceUsed; } else { spaceLeftInBuffer = spaceLeftAfterStreamOff - streamPos; } const int loopStartFileOffset = ComputeFileOffset(info, info.loopStart); const int loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd); if (spaceLeftInBuffer < 0) { // Most likely, the file was truncated. WARN_LOG(Log::Atrac, "File corruption detected: spaceLeftInBuffer < 0: %d. Stumbling along.", spaceLeftInBuffer); spaceLeftInBuffer = 0; } switch (info.state) { case ATRAC_STATUS_STREAMED_WITHOUT_LOOP: { *bytesToWrite = std::clamp(info.fileDataEnd - (info.curFileOff + info.streamDataByte), 0, spaceLeftInBuffer); const int streamFileOff = info.curFileOff + info.streamDataByte; if (streamFileOff < info.fileDataEnd) { *readFileOffset = streamFileOff; *writePtr = info.buffer + WrapAroundRoundedBufferSize(info.streamOff, info.bufferByte, info.streamDataByte, info.sampleSize); } else { *readFileOffset = 0; *writePtr = info.buffer; } break; } case ATRAC_STATUS_STREAMED_LOOP_FROM_END: *bytesToWrite = std::min(ComputeLoopedStreamWritableBytes(info, loopStartFileOffset, loopEndFileOffset), spaceLeftInBuffer); *readFileOffset = IncrementAndLoop(info.curFileOff, info.streamDataByte, loopStartFileOffset, loopEndFileOffset); *writePtr = info.buffer + WrapAroundRoundedBufferSize(info.streamOff, info.bufferByte, info.streamDataByte, info.sampleSize); break; case ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER: { // Behaves like WITHOUT_LOOP or STREAMED_LOOP_FROM_END depending on the decode position. if (info.decodePos <= info.loopEnd) { *bytesToWrite = std::min(ComputeLoopedStreamWritableBytes(info, loopStartFileOffset, loopEndFileOffset), spaceLeftInBuffer); *readFileOffset = IncrementAndLoop(info.curFileOff, info.streamDataByte, loopStartFileOffset, loopEndFileOffset); } else { const int streamFileOff = info.curFileOff + info.streamDataByte; *bytesToWrite = std::clamp(info.fileDataEnd - streamFileOff, 0, spaceLeftInBuffer); if (streamFileOff < info.fileDataEnd) { *readFileOffset = streamFileOff; } else { *readFileOffset = 0; } } if (info.decodePos <= info.loopEnd || info.curBuffer != 1) { *writePtr = info.buffer + WrapAroundRoundedBufferSize(info.streamOff, info.bufferByte, info.streamDataByte, info.sampleSize); } else { *writePtr = info.buffer + WrapAroundRoundedBufferSize(0, info.bufferByte, spaceUsed, info.sampleSize); } break; } default: // unreachable _dbg_assert_(false); break; } } void Atrac2::GetStreamDataInfo(u32 *writePtr, u32 *bytesToWrite, u32 *readFileOffset) { const SceAtracIdInfo &info = context_->info; switch (info.state) { case ATRAC_STATUS_ALL_DATA_LOADED: // Nothing to do, the whole track is loaded already. *writePtr = info.buffer; *bytesToWrite = 0; *readFileOffset = 0; break; case ATRAC_STATUS_HALFWAY_BUFFER: { // This is both the file offset and the offset in the buffer, since it's direct mapped // in this mode (no wrapping or any other trickery). const int fileOffset = (int)info.dataOff + (int)info.streamDataByte; const int bytesLeftInFile = (int)info.fileDataEnd - fileOffset; // Just ask for the rest of the data. The game can supply as much of it as it wants at a time. *writePtr = info.buffer + fileOffset; *bytesToWrite = bytesLeftInFile; *readFileOffset = fileOffset; break; } default: ComputeStreamBufferDataInfo(info, writePtr, bytesToWrite, readFileOffset); break; } } u32 Atrac2::DecodeData(u8 *outbuf, u32 outbufAddr, int *SamplesNum, int *finish, int *remains) { SceAtracIdInfo &info = context_->info; const int tries = info.numSkipFrames + 1; for (int i = 0; i < tries; i++) { u32 result = DecodeInternal(outbufAddr, SamplesNum, finish); if (result != 0) { *SamplesNum = 0; return result; } } *remains = RemainingFrames(); return 0; } u32 Atrac2::DecodeInternal(u32 outbufAddr, int *SamplesNum, int *finish) { SceAtracIdInfo &info = context_->info; // Check that there's enough data to decode. // Check for end of file. const int samplesToDecode = GetNextSamples(); const int nextFileOff = info.curFileOff + info.sampleSize; if (nextFileOff > info.fileDataEnd || info.decodePos > info.endSample) { *finish = 1; return SCE_ERROR_ATRAC_ALL_DATA_DECODED; } DEBUG_LOG(Log::Atrac, "Decode(%08x): samplesToDecode: %d nextFileOff: %d", outbufAddr, samplesToDecode, nextFileOff); // Check for streaming buffer run-out. if (AtracStatusIsStreaming(info.state) && info.streamDataByte < info.sampleSize) { *finish = 0; return SCE_ERROR_ATRAC_BUFFER_IS_EMPTY; } // Check for halfway buffer end. if (info.state == ATRAC_STATUS_HALFWAY_BUFFER && info.dataOff + info.streamDataByte < nextFileOff) { *finish = 0; 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)) { bufferPtr = info.buffer; streamOff = info.curFileOff; } else { const int bufferIndex = info.curBuffer & 1; bufferPtr = bufferIndex == 0 ? info.buffer : info.secondBuffer; streamOff = bufferIndex == 0 ? info.streamOff : info.secondStreamOff; } u32 inAddr = bufferPtr + streamOff; int16_t *outPtr; _dbg_assert_(samplesToDecode <= info.SamplesPerFrame()); if (samplesToDecode != info.SamplesPerFrame()) { if (!decodeTemp_) { decodeTemp_ = new int16_t[info.SamplesPerFrame() * outputChannels_]; } outPtr = decodeTemp_; } else { outPtr = outbufAddr ? (int16_t *)Memory::GetPointer(outbufAddr) : 0; // outbufAddr can be 0 during skip! } context_->codec.inBuf = inAddr; context_->codec.outBuf = outbufAddr; if (!Memory::IsValidAddress(inAddr)) { ERROR_LOG(Log::Atrac, "DecodeInternal: Bad inAddr %08x", inAddr); return SCE_ERROR_ATRAC_API_FAIL; } int bytesConsumed = 0; int outSamples = 0; if (!decoder_->Decode(Memory::GetPointerUnchecked(inAddr), info.sampleSize, &bytesConsumed, outputChannels_, outPtr, &outSamples)) { // Decode failed. *finish = 0; // TODO: The error code here varies based on what the problem is, but not sure of the right values. // 0000020b and 0000020c have been observed for 0xFF and/or garbage data, there may be more codes. context_->codec.err = 0x20b; return SCE_ERROR_ATRAC_API_FAIL; // tested. } else { context_->codec.err = 0; } _dbg_assert_(bytesConsumed == info.sampleSize); // Advance the file offset. info.curFileOff += info.sampleSize; if (info.numSkipFrames == 0) { *SamplesNum = samplesToDecode; if (info.endSample < info.decodePos + samplesToDecode) { *finish = info.loopNum == 0; } else { *finish = 0; } u8 *outBuf = outbufAddr ? Memory::GetPointerWrite(outbufAddr) : nullptr; if (samplesToDecode != info.SamplesPerFrame() && samplesToDecode != 0 && outBuf) { memcpy(outBuf, decodeTemp_, samplesToDecode * outputChannels_ * sizeof(int16_t)); } // Handle increments and looping. info.decodePos += samplesToDecode; if (info.loopEnd != 0 && info.loopNum != 0 && info.decodePos > info.loopEnd) { info.curFileOff = ComputeFileOffset(info, info.loopStart); info.numSkipFrames = ComputeSkipFrames(info, info.loopStart); info.decodePos = info.loopStart; if (info.loopNum > 0) { info.loopNum--; } } } else { info.numSkipFrames--; } // Handle streaming special cases. if (AtracStatusIsStreaming(info.state)) { info.streamDataByte -= info.sampleSize; if (info.curBuffer == 1) { // If currently streaming from the second buffer... int nextStreamOff = info.secondStreamOff + info.sampleSize; if ((int)info.secondBufferByte < nextStreamOff + info.sampleSize) { // Done/ran out info.streamOff = 0; info.secondStreamOff = 0; info.curBuffer = 2; } else { info.secondStreamOff = nextStreamOff; } } else { // Normal streaming from the main buffer. Let's first look at wrapping around the end... const int nextStreamOff = info.streamOff + info.sampleSize; if (nextStreamOff + info.sampleSize > (int)info.bufferByte) { info.streamOff = 0; } else { info.streamOff = nextStreamOff; } // OK, that's the simple stuff done. Moving on to second buffer streaming... // This is quite a condition! // Basically, if we're in state LOOP_WITH_TRAILER and currently streaming from the main buffer, // and either there's no loop or we're just done with the final loop and haven't reached the loop point yet... if (info.state == ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER && info.curBuffer == 0 && (info.loopEnd == 0 || (info.loopNum == 0 && info.loopEnd < info.decodePos))) { // If, at that point, our file streaming offset has indeed reached the loop point... if (info.curFileOff >= ComputeLoopEndFileOffset(info, info.loopEnd)) { // Then we switch to streaming from the secondary buffer, and also let's copy the last partial // packet from the second buffer back to the start of the main buffer... info.curBuffer = 1; info.streamDataByte = info.secondBufferByte; info.secondStreamOff = 0; memcpy(Memory::GetPointerWrite(info.buffer), Memory::GetPointer(info.secondBuffer + (info.secondBufferByte - info.secondBufferByte % info.sampleSize)), info.secondBufferByte % info.sampleSize); } } } } return 0; } int Atrac2::SetData(const Track &track, u32 bufferAddr, u32 readSize, u32 bufferSize, u32 fileSize, int outputChannels, bool isAA3) { _dbg_assert_(outputChannels == 1 || outputChannels == 2); TrackInfo trackInfo{}; if (Memory::IsValidAddress(bufferAddr)) { // Turns out that games can abuse bufferSize, so we can't verify that it's a valid length with GetPointerRange. const u8 *bufferPtr = Memory::GetPointerUnchecked(bufferAddr); if (!Memory::IsValidRange(bufferAddr, readSize)) { WARN_LOG(Log::Atrac, "Atrac2::SetData: Bad buffer range %08x+%08x - however, proceeeding.", bufferAddr, readSize); } if (!isAA3) { int retval = ParseWaveAT3(bufferPtr, readSize, &trackInfo); if (retval < 0) { ERROR_LOG(Log::Atrac, "Atrac2::SetData: ParseWaveAT3 failed with %08x", retval); return retval; } } else { int retval = ParseAA3(bufferPtr, readSize, fileSize, &trackInfo); if (retval < 0) { ERROR_LOG(Log::Atrac, "Atrac2::SetData: ParseAA3 failed with %08x", retval); return retval; } } } int retval = InitContextFromTrackInfo(context_, &trackInfo, bufferAddr, readSize, bufferSize); if (retval < 0) { ERROR_LOG(Log::Atrac, "Atrac2::SetData: InitContextFromTrackInfo failed with %08x", retval); return retval; } SceAtracIdInfo &info = context_->info; CreateDecoder(info.codec, info.sampleSize, info.numChan); outputChannels_ = outputChannels; INFO_LOG(Log::Atrac, "Atrac: sampleSize: %d buffer: %08x bufferByte: %d firstValidSample: %d\n" "endSample: %d loopStart: %d loopEnd: %d\n" "dataOff: %d curFileOff: %d streamOff: %d streamDataByte: %d\n" "fileDataEnd: %d decodePos: %d numSkipFrames: %d channels: %d", info.sampleSize, info.buffer, info.bufferByte, info.firstValidSample, info.endSample, info.loopStart, info.loopEnd, info.dataOff, info.curFileOff, info.streamOff, info.streamDataByte, info.fileDataEnd, info.decodePos, info.numSkipFrames, info.numChan ); int skipCount = 0; // TODO: use for delay retval = SkipFrames(&skipCount); // Seen in Mui Mui house. Things go very wrong after this.. if (retval == SCE_ERROR_ATRAC_API_FAIL) { ERROR_LOG(Log::Atrac, "Bad frame during initial skip"); } else if (retval != 0) { ERROR_LOG(Log::Atrac, "SkipFrames during InitContext returned an error: %08x", retval); } WrapLastPacket(); return retval; } void Atrac2::WrapLastPacket() { SceAtracIdInfo &info = context_->info; // 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); } } u32 Atrac2::SkipFrames(int *skipCount) { SceAtracIdInfo &info = context_->info; *skipCount = 0; int finishIgnored; while (true) { if (info.numSkipFrames == 0) { return 0; } u32 retval = DecodeInternal(0, 0, &finishIgnored); if (retval != 0) { if (retval == SCE_ERROR_ATRAC_API_FAIL) { WARN_LOG(Log::Atrac, "Failed during skip-frame, ignoring: %08x", retval); (*skipCount)++; } return retval; } else { DEBUG_LOG(Log::Atrac, "Frame correctly decoded during skip. numSkipFrames == %d", info.numSkipFrames); } (*skipCount)++; } return 0; } // Where to read from to fill the second buffer. int Atrac2::GetSecondBufferInfo(u32 *fileOffset, u32 *readSize) const { const SceAtracIdInfo &info = context_->info; if (info.state != ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER) { // No second buffer needed in this state. *fileOffset = 0; *readSize = 0; return SCE_ERROR_ATRAC_SECOND_BUFFER_NOT_NEEDED; } const int loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd); *fileOffset = loopEndFileOffset; *readSize = info.fileDataEnd - loopEndFileOffset; return 0; } int Atrac2::SetSecondBuffer(u32 secondBuffer, u32 secondBufferSize) { SceAtracIdInfo &info = context_->info; u32 loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd); if ((info.sampleSize * 3 <= (int)secondBufferSize || (info.fileDataEnd - loopEndFileOffset) <= (int)secondBufferSize)) { if (info.state == ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER) { info.secondBuffer = secondBuffer; info.secondBufferByte = secondBufferSize; info.secondStreamOff = 0; return 0; } else { return SCE_ERROR_ATRAC_SECOND_BUFFER_NOT_NEEDED; } } return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; } u32 Atrac2::GetInternalCodecError() const { if (context_.IsValid()) { return context_->codec.err; } else { return 0; } } int Atrac2::Bitrate() const { const SceAtracIdInfo &info = context_->info; return info.BitRate(); } void Atrac2::InitLowLevel(const Atrac3LowLevelParams ¶ms, int codecType) { SceAtracIdInfo &info = context_->info; info.codec = codecType; info.numChan = params.encodedChannels; outputChannels_ = params.outputChannels; info.sampleSize = params.bytesPerFrame; info.dataOff = 0; info.decodePos = 0; info.state = ATRAC_STATUS_LOW_LEVEL; CreateDecoder(codecType, info.sampleSize, info.numChan); } int Atrac2::DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData, int *bytesWritten) { SceAtracIdInfo &info = context_->info; const int channels = outputChannels_; int outSamples = 0; bool success = decoder_->Decode(srcData, info.sampleSize, bytesConsumed, channels, dstData, &outSamples); if (!success) { ERROR_LOG(Log::Atrac, "Low level decoding failed: sampleSize: %d bytesConsumed: %d", info.sampleSize, *bytesConsumed); // We proceed anyway, see issue #20452 /* *bytesConsumed = 0; *bytesWritten = 0; return SCE_ERROR_ATRAC_API_FAIL; // need to check what return value we get here. */ } *bytesWritten = outSamples * channels * sizeof(int16_t); // TODO: Possibly return a decode error on bad data. return 0; } void Atrac2::CheckForSas() { SceAtracIdInfo &info = context_->info; if (info.numChan != 1) { WARN_LOG(Log::Atrac, "Caller forgot to set channels to 1"); } if (info.state != 0x10) { WARN_LOG(Log::Atrac, "Caller forgot to set state to 0x10"); } sas_.isStreaming = info.fileDataEnd > (s32)info.bufferByte; if (sas_.isStreaming) { INFO_LOG(Log::Atrac, "SasAtrac stream mode"); } else { INFO_LOG(Log::Atrac, "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::Atrac, "Caller tries to send us a zero buffer. Something went wrong."); } DEBUG_LOG(Log::Atrac, "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) { 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 + (int)info.sampleSize <= (int)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::Atrac, "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::Atrac, "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); } // Check if we hit the end. if (sas_.fileOffset >= info.fileDataEnd) { DEBUG_LOG(Log::Atrac, "Streaming and hit the file end."); *bytesWritten = 0; *finish = 1; return; } // Check that a new buffer actually exists if (info.secondBuffer == sas_.bufPtr[sas_.curBuffer]) { ERROR_LOG(Log::Atrac, "Can't enqueue the same buffer twice in a row!"); *bytesWritten = 0; *finish = 1; return; } if ((int)info.secondBuffer < 0) { ERROR_LOG(Log::Atrac, "AtracSas streaming ran out of data, no secondbuffer pending"); *bytesWritten = 0; *finish = 1; return; } // 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::Atrac, "%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::Atrac, "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::Atrac, "SAS failed to decode assembled packet"); } } }