Some new findings, loop work

This commit is contained in:
Henrik Rydgård 2025-03-14 09:32:25 +01:00
parent 4d7a8b8233
commit b99348383c
8 changed files with 92 additions and 15 deletions

View file

@ -128,7 +128,8 @@ struct Track {
}
inline int FirstOffsetExtra() const {
return codecType == PSP_MODE_AT_3_PLUS ? 368 : 69;
// These first samples are skipped, after first possibly skipping 0-2 full frames, it seems.
return codecType == PSP_MODE_AT_3_PLUS ? 0x170 : 0x45;
}
// Includes the extra offset. See firstSampleOffset comment above.

View file

@ -91,6 +91,7 @@ int Atrac2::RemainingFrames() const {
}
// Since we're streaming, the remaining frames are what's valid in the buffer.
// NOTE: If read wraps around the buffer, we need to decrement by one, it seems.
return info.streamDataByte / info.sampleSize;
}
@ -329,14 +330,69 @@ void Atrac2::GetStreamDataInfo(u32 *writePtr, u32 *bytesToRead, u32 *readFileOff
// from just logs of variables wasn't all that easy... Initially I had it more complicated, but boiled it
// all down to fairly simple logic. And then boiled it down further and fixed bugs.
//
// TODO: Take care of loop points.
// And then had to make it complicated again to support looping... Sigh.
const int fileOffset = (int)info.curFileOff + (int)info.streamDataByte;
const int bytesLeftInFile = (int)info.fileDataEnd - fileOffset;
auto LoopPointToFileOffset = [&](int loop, bool roundUp) -> int {
if (roundUp) {
loop += info.sampleSize - 1;
}
return info.dataOff + (loop / track_.SamplesPerFrame()) * info.sampleSize;
};
_dbg_assert_(bytesLeftInFile >= 0);
// These are mutable so the looping code can make another decision.
int fileOffset = -1;
int bytesLeftToRead = -1;
if (bytesLeftInFile == 0) {
bool looped = info.loopNum != 0;
if (looped) {
// We rewind back to one frame before the loopstart.
// Not sure about this math but it works for the test so can fix it later!
const int loopStartFileOffset = LoopPointToFileOffset(info.loopStart, false) - info.sampleSize;
const int loopEndFileOffset = LoopPointToFileOffset(info.firstValidSample + info.loopEnd, true);
// We also need to loop the streaming.
switch (info.state) {
case ATRAC_STATUS_STREAMED_LOOP_FROM_END:
case ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER:
{
// Let's ignore the tail for now, and just get some logic to work.
// The playback logic has to match this.
fileOffset = (int)info.curFileOff + (int)info.streamDataByte;
int streamFirstPart = 0;
if (fileOffset == loopEndFileOffset) {
INFO_LOG(Log::ME, "Atrac: Hit the stream buffer wrap point (buffering)");
}
if (fileOffset >= loopEndFileOffset) {
// We have the data up to the loop already, so wrap the read around.
int secondPart = fileOffset - loopEndFileOffset;
int firstPart = info.streamDataByte - secondPart;
_dbg_assert_(firstPart >= 0);
_dbg_assert_(secondPart >= 0);
bytesLeftToRead = loopEndFileOffset - secondPart;
fileOffset = loopStartFileOffset + secondPart;
} else {
bytesLeftToRead = loopEndFileOffset - fileOffset;
}
_dbg_assert_(bytesLeftToRead >= 0);
break;
}
case ATRAC_STATUS_ALL_DATA_LOADED:
case ATRAC_STATUS_HALFWAY_BUFFER:
default:
// TODO
_dbg_assert_(false);
break;
}
} else {
// Non-looped. Basic math.
fileOffset = (int)info.curFileOff + (int)info.streamDataByte;
bytesLeftToRead = (int)info.fileDataEnd - fileOffset;
_dbg_assert_(bytesLeftToRead >= 0);
}
_dbg_assert_(bytesLeftToRead >= 0 && fileOffset >= 0);
if (bytesLeftToRead == 0) {
// We've got all the data up to the end buffered, no more streaming is needed.
// Signalling this by setting everything to default.
*writePtr = info.buffer;
@ -352,7 +408,7 @@ void Atrac2::GetStreamDataInfo(u32 *writePtr, u32 *bytesToRead, u32 *readFileOff
// There's space left without wrapping.
const int writeOffset = info.streamOff + info.streamDataByte;
*writePtr = info.buffer + writeOffset;
*bytesToRead = std::min(distanceToEnd - (int)info.streamDataByte, bytesLeftInFile);
*bytesToRead = std::min(distanceToEnd - (int)info.streamDataByte, bytesLeftToRead);
*readFileOffset = *bytesToRead == 0 ? 0 : fileOffset; // Seems this behavior (which isn't important) only happens on this path?
} else {
// Wraps around.
@ -360,7 +416,7 @@ void Atrac2::GetStreamDataInfo(u32 *writePtr, u32 *bytesToRead, u32 *readFileOff
const int secondPart = info.streamDataByte - firstPart;
const int spaceLeft = info.streamOff - secondPart;
*writePtr = info.buffer + secondPart;
*bytesToRead = std::min(spaceLeft, bytesLeftInFile);
*bytesToRead = std::min(spaceLeft, bytesLeftToRead);
*readFileOffset = fileOffset;
}
break;
@ -470,18 +526,29 @@ u32 Atrac2::DecodeData(u8 *outbuf, u32 outbufPtr, u32 *SamplesNum, u32 *finish,
info.curFileOff += info.sampleSize;
info.decodePos += decodePosAdvance;
} else {
// Handle looping differently depending on state.
if (info.state == ATRAC_STATUS_ALL_DATA_LOADED) {
// Handle looping differently depending on state (actually seems pretty similar...)
switch (info.state) {
case ATRAC_STATUS_ALL_DATA_LOADED:
case ATRAC_STATUS_STREAMED_LOOP_FROM_END:
// Curiously this is exactly the same.
info.decodePos = info.loopStart;
info.curFileOff = info.dataOff + (info.loopStart / track_.SamplesPerFrame()) * info.sampleSize; // for some reason??? Not the same as the first frame after SetData.
info.numFrame = 1; // not sure what this is about, but in testing it's there.
if (info.loopNum > 0) {
info.loopNum--;
}
} else {
break;
case ATRAC_STATUS_LOW_LEVEL:
case ATRAC_STATUS_FOR_SCESAS:
case ATRAC_STATUS_STREAMED_WITHOUT_LOOP:
_dbg_assert_msg_or_log_(false, Log::ME, "Hit a loop end, but is in state %s. This shouldn't happen.", AtracStatusToString(info.state));
break;
default:
ERROR_LOG(Log::ME, "Looping in state %s not yet supported", AtracStatusToString(info.state));
// Fallback to just ending.
info.curFileOff += info.sampleSize;
info.decodePos += decodePosAdvance;
break;
}
}

View file

@ -393,6 +393,7 @@ enum PSPErrorCode : u32 {
SCE_ERROR_ATRAC_SIZE_TOO_SMALL = 0x80630011,
SCE_ERROR_ATRAC_SECOND_BUFFER_NEEDED = 0x80630012,
SCE_ERROR_ATRAC_INCORRECT_READ_SIZE = 0x80630013,
SCE_ERROR_ATRAC_BAD_ALIGNMENT = 0x80630014,
SCE_ERROR_ATRAC_BAD_SAMPLE = 0x80630015,
SCE_ERROR_ATRAC_BAD_FIRST_RESET_SIZE = 0x80630016,
SCE_ERROR_ATRAC_BAD_SECOND_RESET_SIZE = 0x80630017,

View file

@ -281,6 +281,10 @@ static u32 sceAtracDecodeData(int atracID, u32 outAddr, u32 numSamplesAddr, u32
return hleLogError(Log::ME, err);
}
if (outAddr & 1) {
return hleLogError(Log::ME, SCE_ERROR_ATRAC_BAD_ALIGNMENT);
}
u32 numSamples = 0;
u32 finish = 0;
int remains = 0;
@ -914,6 +918,8 @@ static int sceAtracSetAA3DataAndGetID(u32 buffer, u32 bufferSize, u32 fileSize,
return hleDelayResult(hleLogDebug(Log::ME, atracID), "atrac set aa3 data", 100);
}
// TODO: Should see if these are stored contiguously in memory somewhere, or if there really are
// individual allocations being used.
static u32 _sceAtracGetContextAddress(int atracID) {
AtracBase *atrac = getAtrac(atracID);
if (!atrac) {

View file

@ -70,9 +70,9 @@ struct SceAtracIdInfo {
s32 loopStart; // Start of the loop (sample index)
s32 loopEnd; // End of the loop (sample index)
s32 firstValidSample; // Seems to be the number of skipped samples at the start. After SetID, decodePos will match this. Was previously misnamed 'samplesPerChan'.
u8 numFrame; // This is 1 for a single frame when a loop is triggered, otherwise seems to stay at 0. Likely mis-named.
u8 framesToSkip; // This is 1 for a single frame when a loop is triggered, otherwise seems to stay at 0. Likely mis-named.
AtracStatus state; // State enum, see AtracStatus.
u8 unk22; // UNKNOWN: Always zero?
u8 curBuffer; // Current buffer (1 == second, 2 == done?) Previously unk
u8 numChan; // Number of audio channels, usually 2 but 1 is possible.
u16 sampleSize; // Size in bytes of an encoded audio frame.
u16 codec; // Codec. 0x1000 is Atrac3+, 0x1001 is Atrac3. See the PSP_CODEC_ enum (only these two are supported).
@ -82,7 +82,7 @@ struct SceAtracIdInfo {
s32 loopNum; // Current loop counter. If 0, will not loop. -1 loops for ever, positive numbers get decremented on the loop end. So to play a song 3 times and then end, set this to 2.
s32 streamDataByte; // Number of bytes of queued/buffered/uploaded data. In full and half-way modes, this isn't decremented as you decode.
s32 streamOff; // Streaming modes only: The byte offset inside the RAM buffer where sceAtracDecodeData will read from next. ONLY points to even packet boundaries.
u32 unk52; // UNKNOWN: Always zero?
u32 secondStreamOff; // Secondary streamoff? previously unk.
u32 buffer; // Address in RAM of the main buffer.
u32 secondBuffer; // Address in RAM of the second buffer, or 0 if not used.
u32 bufferByte; // Size in bytes of the main buffer.

View file

@ -32,7 +32,7 @@ struct SceAudiocodecCodec {
s32 srcFrameSize; // 1c
u32 outBuf; // 20 // This is where the decoded data is written.
s32 dstBytesWritten; // 24
s8 unk40; // 28 format or looping related
s8 unk40; // 28 format or looping related // Probably, from here on out is a union with different fields for different codecs.
s8 unk41; // 29 format or looping related
s16 unk42; // 2a
s8 unk44;

View file

@ -1425,6 +1425,7 @@ const char *KernelErrorToString(u32 err) {
case SCE_ERROR_ATRAC_IS_FOR_SCESAS: return "SCE_ERROR_ATRAC_IS_FOR_SCESAS";
case SCE_ERROR_ATRAC_AA3_INVALID_DATA: return "SCE_ERROR_ATRAC_AA3_INVALID_DATA";
case SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL: return "SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL";
case SCE_ERROR_ATRAC_BAD_ALIGNMENT: return "SCE_ERROR_ATRAC_BAD_ALIGNMENT";
default:
return nullptr;

View file

@ -382,6 +382,7 @@ tests_next = [
"cpu/vfpu/vector",
"cpu/vfpu/vregs",
"audio/atrac/replay",
"audio/atrac/stream",
"audio/atrac/second/resetting",
"audio/sceaudio/datalen",
"audio/sceaudio/output",