diff --git a/Core/HLE/AtracBase.h b/Core/HLE/AtracBase.h index 3f78686464..c6b6c96619 100644 --- a/Core/HLE/AtracBase.h +++ b/Core/HLE/AtracBase.h @@ -88,6 +88,15 @@ struct SceAtracIdInfo { // These first samples are skipped, after first possibly skipping 0-2 full frames, it seems. return codec == 0x1000 ? 0x170 : 0x45; } + int BitRate() const { + int bitrate = (sampleSize * 352800) / 1000; + if (codec == PSP_CODEC_AT3PLUS) { + bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0; + } else { + bitrate = (bitrate + 511) >> 10; + } + return bitrate; + } }; // One of these structs is allocated for each Atrac context. diff --git a/Core/HLE/AtracCtx2.cpp b/Core/HLE/AtracCtx2.cpp index d84ff414df..f2bceedfcf 100644 --- a/Core/HLE/AtracCtx2.cpp +++ b/Core/HLE/AtracCtx2.cpp @@ -31,6 +31,21 @@ // TODO: Add an AT3 dumping facility (and/or replacement?). Although the old implementation could do it more easily... + +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); @@ -131,6 +146,118 @@ static int ComputeRemainFrameLooped(const SceAtracIdInfo &info) { 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 * 3) { + return 0x80630011; + } + 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; @@ -836,105 +963,27 @@ u32 Atrac2::DecodeInternal(u32 outbufAddr, int *SamplesNum, int *finish) { } int Atrac2::SetData(const Track &track, u32 bufferAddr, u32 readSize, u32 bufferSize, int outputChannels) { - // 72 is about the size of the minimum required data to even be valid. - if (readSize < 72) { - return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; + TrackInfo trackInfo{}; + if (bufferAddr) { + int retval = ParseWaveAT3(Memory::GetPointerRange(bufferAddr, bufferSize), readSize, &trackInfo); + if (retval < 0) { + ERROR_LOG(Log::Atrac, "Atrac2::SetData: ParseWaveAT3 failed with %08x", retval); + return retval; + } } - // TODO: Check the range (addr, size) instead. - if (!Memory::IsValidAddress(bufferAddr)) { - return SCE_KERNEL_ERROR_ILLEGAL_ADDRESS; + int retval = InitContextFromTrackInfo(context_, &trackInfo, bufferAddr, readSize, bufferSize); + if (retval < 0) { + ERROR_LOG(Log::Atrac, "Atrac2::SetData: InitContextFromTrackInfo failed with %08x", retval); + return retval; } - track.DebugLog(); - SceAtracIdInfo &info = context_->info; - if (track.codecType != PSP_CODEC_AT3 && track.codecType != PSP_CODEC_AT3PLUS) { - // Shouldn't have gotten here, Analyze() checks this. - ERROR_LOG(Log::Atrac, "unexpected codec type %d in set data", track.codecType); - return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; - } - - if (outputChannels != track.channels) { - INFO_LOG(Log::Atrac, "Atrac2::SetData: outputChannels %d doesn't match track_.channels %d, decoder will expand.", outputChannels, track.channels); - } - - if (readSize >= track.fileSize) { - INFO_LOG(Log::Atrac, "The full file was set directly - we can dump it."); - char filename[512]; - snprintf(filename, sizeof(filename), "%s_%d%s", track.codecType == PSP_CODEC_AT3 ? "at3" : "at3plus", track.endSample, track.channels == 1 ? "_mono" : ""); - DumpFileIfEnabled(Memory::GetPointer(bufferAddr), readSize, filename, DumpFileType::Atrac3); - } else if (false && (int)g_Config.iDumpFileTypes & (int)DumpFileType::Atrac3) { - // TODO: This is disabled (using the false above) until we can properly append streamed data to the dump. - // Still can be useful for debugging by dumping the start of the file, so leaving it here. - dumpBuffer_.resize(readSize); - Memory::Memcpy(dumpBuffer_.data(), bufferAddr, readSize); - } - - CreateDecoder(track.codecType, track.bytesPerFrame, track.channels); + CreateDecoder(info.codec, info.sampleSize, info.numChan); outputChannels_ = outputChannels; - if (!track.loopinfo.empty()) { - info.loopNum = track.loopinfo[0].playCount; - info.loopStart = track.loopStartSample; - info.loopEnd = track.loopEndSample; - } else { - info.loopNum = 0; - info.loopStart = 0; - info.loopEnd = 0; - } - - context_->codec.inBuf = bufferAddr; - - if (readSize > track.fileSize) { - INFO_LOG(Log::Atrac, "readSize (%d) > track_.fileSize (%d)", readSize, track.fileSize); - } - - if (bufferSize >= track.fileSize) { - // Buffer is big enough to fit the whole track. - if (readSize < bufferSize) { - info.state = ATRAC_STATUS_HALFWAY_BUFFER; - } else { - info.state = ATRAC_STATUS_ALL_DATA_LOADED; - } - } else { - // Streaming cases with various looping types. - if (track.loopEndSample <= 0) { - // There's no looping, but we need to stream the data in our buffer. - info.state = ATRAC_STATUS_STREAMED_WITHOUT_LOOP; - } else if (track.loopEndSample == track.endSample + track.FirstSampleOffsetFull()) { - info.state = ATRAC_STATUS_STREAMED_LOOP_FROM_END; - } else { - info.state = ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER; - } - } - - DEBUG_LOG(Log::Atrac, "Atrac mode setup: %s", AtracStatusToString(info.state)); - - // Copy parameters into state struct. - info.codec = track.codecType; - info.numChan = track.channels; - info.sampleSize = track.bytesPerFrame; - info.buffer = bufferAddr; - info.bufferByte = bufferSize; - info.firstValidSample = track.FirstSampleOffsetFull(); - info.endSample = track.endSample + info.firstValidSample; - if (track.loopStartSample != 0xFFFFFFFF) { - info.loopStart = track.loopStartSample; - info.loopEnd = track.loopEndSample; - // Sanity check the loop points, useful for testing. - } - info.dataOff = track.dataByteOffset; - info.curFileOff = track.dataByteOffset; - info.streamOff = track.dataByteOffset; - info.streamDataByte = readSize - info.dataOff; - info.fileDataEnd = track.fileSize; - info.decodePos = track.FirstSampleOffsetFull(); - info.numSkipFrames = info.firstValidSample / info.SamplesPerFrame(); - // NOTE: we do not write into secondBuffer/secondBufferByte! they linger... - INFO_LOG(Log::Atrac, "Atrac: sampleSize: %d buffer: %08x bufferByte: %d firstValidSample: %d\n" "endSample: %d loopStart: %d loopEnd: %d\n" @@ -947,7 +996,7 @@ int Atrac2::SetData(const Track &track, u32 bufferAddr, u32 readSize, u32 buffer ); int skipCount = 0; // TODO: use for delay - u32 retval = SkipFrames(&skipCount); + retval = SkipFrames(&skipCount); // Seen in Mui Mui house. Things go very wrong after this.. if (retval == SCE_ERROR_ATRAC_API_FAIL) { @@ -962,26 +1011,28 @@ int Atrac2::SetData(const Track &track, u32 bufferAddr, u32 readSize, u32 buffer void Atrac2::WrapLastPacket() { SceAtracIdInfo &info = context_->info; - // We need to handle wrapping the overshot partial packet at the end. - if (AtracStatusIsStreaming(info.state)) { - // 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); - } + // 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); } } @@ -1052,13 +1103,7 @@ u32 Atrac2::GetInternalCodecError() const { int Atrac2::Bitrate() const { const SceAtracIdInfo &info = context_->info; - - int bitrate = (info.sampleSize * 352800) / 1000; - if (info.codec == PSP_CODEC_AT3PLUS) - bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0; - else - bitrate = (bitrate + 511) >> 10; - return bitrate; + return info.BitRate(); } void Atrac2::InitLowLevel(const Atrac3LowLevelParams ¶ms, int codecType) { diff --git a/Core/HLE/sceAudiocodec.cpp b/Core/HLE/sceAudiocodec.cpp index bb3ad5022e..a8ae37276a 100644 --- a/Core/HLE/sceAudiocodec.cpp +++ b/Core/HLE/sceAudiocodec.cpp @@ -50,7 +50,14 @@ static_assert(sizeof(SceAudiocodecCodec) == 128); // Known byte values. // Atrac3 (0x1001) -// ------------------------------------------------ +// +// Frame size data byte JointStereo? +// ------------------------------------------------- +// 0x180 0x04 0 +// 0x130 0x06 0 +// 0x0C0 0x0B 1 +// 0x0C0 0x0E 0 +// 0x098 0x0F 0 // AAC (0x1003) // ------------------------------------------------ diff --git a/Core/HLE/sceAudiocodec.h b/Core/HLE/sceAudiocodec.h index 4d24a98e2b..ac3aaf9c45 100644 --- a/Core/HLE/sceAudiocodec.h +++ b/Core/HLE/sceAudiocodec.h @@ -44,8 +44,8 @@ struct SceAudiocodecCodec { // Probably, from here on out is a union with different fields for different codecs. union { // offset 40 / 0x28 struct { - s8 unk40; // 28 format or looping related - s8 unk41; // 29 format or looping related + s8 unk40; // 28 format or looping related . Aka tailrelated + s8 unk41; // 29 format or looping related . Aka tailflag s8 unk42; s8 unk43; }; diff --git a/Core/Util/AtracTrack.cpp b/Core/Util/AtracTrack.cpp index 86586ba863..f2a00fd5f9 100644 --- a/Core/Util/AtracTrack.cpp +++ b/Core/Util/AtracTrack.cpp @@ -2,18 +2,20 @@ #include "Common/StringUtils.h" #include "Core/Util/AtracTrack.h" #include "Core/HLE/ErrorCodes.h" +#include "Common/Data/Format/RIFF.h" #include "Core/MemMap.h" // Atrac file parsing constants -#define AT3_MAGIC 0x0270 -#define AT3_PLUS_MAGIC 0xFFFE // This is normally a marker for WAVE_FORMAT_EXTENSIBLE -const int RIFF_CHUNK_MAGIC = 0x46464952; -const int RIFF_WAVE_MAGIC = 0x45564157; -const int FMT_CHUNK_MAGIC = 0x20746D66; -const int DATA_CHUNK_MAGIC = 0x61746164; -const int SMPL_CHUNK_MAGIC = 0x6C706D73; -const int FACT_CHUNK_MAGIC = 0x74636166; +constexpr int RIFF_CHUNK_MAGIC = 0x46464952; +constexpr int RIFF_WAVE_MAGIC = 0x45564157; +constexpr int FMT_CHUNK_MAGIC = 0x20746D66; +constexpr int DATA_CHUNK_MAGIC = 0x61746164; +constexpr int SMPL_CHUNK_MAGIC = 0x6C706D73; +constexpr int FACT_CHUNK_MAGIC = 0x74636166; + +constexpr u32 WAVE_FORMAT_EXTENSIBLE = 0xFFFE; +constexpr u32 WAVE_FORMAT_AT3 = 0x270; static u16 Read16(const u8 *buffer, int offset) { u16 value; @@ -27,6 +29,7 @@ static u32 Read32(const u8 *buffer, int offset) { return value; } +// Old WAVE parser. int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *error) { // 72 is about the size of the minimum required data to even be valid. if (size < 72) { @@ -108,14 +111,14 @@ int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *err } auto at3fmt = (const RIFFFmtChunk *)(buffer + offset); - if (chunkSize < 32 || (at3fmt->fmtTag == AT3_PLUS_MAGIC && chunkSize < 52)) { + if (chunkSize < 32 || (at3fmt->fmtTag == WAVE_FORMAT_EXTENSIBLE && chunkSize < 52)) { *error = "AnalyzeTrack: fmt definition too small(%d)"; return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } - if (at3fmt->fmtTag == AT3_MAGIC) + if (at3fmt->fmtTag == WAVE_FORMAT_AT3) track->codecType = PSP_CODEC_AT3; - else if (at3fmt->fmtTag == AT3_PLUS_MAGIC) + else if (at3fmt->fmtTag == WAVE_FORMAT_EXTENSIBLE) track->codecType = PSP_CODEC_AT3PLUS; else { *error = "AnalyzeTrack: invalid fmt magic: %04x"; @@ -140,13 +143,12 @@ int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *err // TODO: There are some format specific bytes here which seem to have fixed values? // Probably don't need them. - if (at3fmt->fmtTag == AT3_MAGIC) { // 0x270 + if (at3fmt->fmtTag == WAVE_FORMAT_AT3) { // 0x270 // This is the offset to the jointStereo_ field. track->jointStereo = Read16(buffer, offset + 24); // Then there are more fields here. u16 unknown1_2 = Read16(buffer, offset + 30); - } else if (at3fmt->fmtTag == AT3_PLUS_MAGIC) { - _dbg_assert_(at3fmt->fmtTag == 0xFFFE); + } else if (at3fmt->fmtTag == WAVE_FORMAT_EXTENSIBLE) { // It's in an "Extensible" wave format. Let's read some more. } if (chunkSize > 16) { @@ -321,3 +323,217 @@ int AnalyzeAA3Track(const u8 *buffer, u32 size, u32 fileSize, Track *track, std: track->endSample -= 1; return 0; } + +static inline u32 Read16(const u8 *base, int *offset) { + // Little-endian reliance. + u32 value = 0; + memcpy(&value, base + *offset, 2); + *offset += 2; + return value; +} + +static inline u32 Read32(const u8 *base, int *offset) { + // Little-endian reliance. + u32 value = 0; + memcpy(&value, base + *offset, 4); + *offset += 4; + return value; +} + +static const u8 g_atrac3Checkbytes[] = { + 0xBF, 0xAA, 0x23, 0xE9, 0x58, 0xCB, 0x71, 0x44, + 0xA1, 0x19, 0xFF, 0xFA, 0x01, 0xE4, 0xCE, 0x62, +}; + +static inline int RoundUpToEven(int size) { + // Round up to the next even number. + return (size + 1) & ~1; +} + +int ParseWaveAT3(const u8 *data, int dataLength, TrackInfo *track) { + track->loopStart = 0xFFFFFFFF; + track->loopEnd = 0xFFFFFFFF; + track->firstSampleOffset = 0; + track->endSample = 0; + track->waveDataSize = 0; + + int retval = SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + int offset = 0; // byte offset into the data array. Is kept even. + + // Scan RIFF chunks for the RIFFWAVE header. Normally we find this immediately. + while (true) { + if (offset + 0xC >= dataLength) { + return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; + } + const u32 blockID = Read32(data, &offset); + if (blockID != RIFF_CHUNK_MAGIC) { + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + const u32 blockSize = RoundUpToEven(Read32(data, &offset)); + const u32 waveID = Read32(data, &offset); + if (waveID == RIFF_WAVE_MAGIC) { + // We found the WAVE header. + break; + } + offset += blockSize - 4; + } + + // Now that we have the header, loop through the rest of the chunks. + bool modifiedSampleOffset = false; + while (true) { + if (offset + 8 >= dataLength) { + return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; + } + const u32 chunkID = Read32(data, &offset); + const int chunkSize = RoundUpToEven(Read32(data, &offset)); + const int nextOffset = offset + chunkSize; + // We allow the data chunk to be bigger than readSize, as we may not have read the whole file. + if (offset + chunkSize > dataLength && chunkID != DATA_CHUNK_MAGIC) { + return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; + } + int remainingBytes = 0; + switch (chunkID) { + case DATA_CHUNK_MAGIC: + { + // This should be the last chunk we find, it lasts to the end of the file. + // I guess another order is possible, but not if streaming. + track->waveDataSize = chunkSize; + track->dataOff = offset; + if (!track->firstSampleOffset) { + INFO_LOG(Log::Atrac, "DATA chunk found at offset %d with size %d", offset, chunkSize); + track->firstSampleOffset = retval == PSP_CODEC_AT3 ? 0x400 : 0x800; + } + if (modifiedSampleOffset && retval == PSP_CODEC_AT3PLUS) { + track->firstSampleOffset -= 0xb8; + if (track->loopEnd != 0xFFFFFFFF) { + track->loopEnd -= 0xb8; + track->loopStart -= 0xb8; + } + } + return retval; + } + case FMT_CHUNK_MAGIC: + { + if (retval != SCE_ERROR_ATRAC_UNKNOWN_FORMAT) { + // This means dual FMT chunks, which is not valid. + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + if (chunkSize < 0x20) { + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + const u16 fmtTag = Read16(data, &offset); + const u16 chans = Read16(data, &offset); + track->numChans = chans; + if (chans != 1 && chans != 2) { + // Only stereo and mono are supported. + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + const int sampleRate = Read32(data, &offset); + if (sampleRate != 44100) { + // Only a single sample rate is supported. + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + offset += 4; // skip avgBytesPerSec + track->blockAlign = (u16)Read16(data, &offset); + if (track->blockAlign == 0) { + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + if (fmtTag == WAVE_FORMAT_AT3) { + // Atrac3 format. + offset += 4; // skip the extra bytes + const u16 jointStereo = Read16(data, &offset); + if (jointStereo != 1) { + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + offset += 4; + const u16 sampleSizeTailFlag = Read16(data, &offset); + track->sampleSizeMaybe = (u8)sampleSizeTailFlag; + track->tailFlag = (u8)(sampleSizeTailFlag >> 8); + const u16 unknown2 = Read16(data, &offset); + if ((sampleSizeTailFlag & 0xffff) != unknown2) { + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + // Don't what this is, but it seems to be always 1. + const u32 supposedToBeOne = Read32(data, &offset); + if (supposedToBeOne != 1) { + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + retval = PSP_CODEC_AT3; // ATRAC3 + remainingBytes = chunkSize - 0x20; + } else if (fmtTag == WAVE_FORMAT_EXTENSIBLE) { + // Atrac3+ format, probably. + if (chunkSize < 0x34) { + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + if (memcmp(data + offset + 10, g_atrac3Checkbytes, sizeof(g_atrac3Checkbytes)) != 0) { + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + track->sampleSizeMaybe = data[offset + 0x1c]; + track->tailFlag = data[offset + 0x1d]; + if ((track->sampleSizeMaybe << 27) >> 29 != track->numChans) { + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + offset += 0x26; // Or, use the blocksize? + retval = PSP_CODEC_AT3PLUS; + remainingBytes = chunkSize - 0x34; + } else { + // Unsupported fmtTag. + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + if (remainingBytes) { + INFO_LOG(Log::Atrac, "FMT chunk has %d extra bytes", remainingBytes); + } + break; + } + case SMPL_CHUNK_MAGIC: + { + if ((int)track->loopStart < 0) { + if (chunkSize < 0x20) { + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + offset += 0x1c; + const int numLoops = Read32(data, &offset); + if (numLoops != 0) { + if (chunkSize < 0x34) { + return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; + } + offset += 0xc; + track->loopStart = Read32(data, &offset); + track->loopEnd = Read32(data, &offset); + if (track->loopEnd <= (int)track->loopStart) { + return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS; + } + } + } + break; + } + case FACT_CHUNK_MAGIC: + { + if (chunkSize < 4) { + return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; + } + track->endSample = Read32(data, &offset); + remainingBytes = chunkSize - 4; + if (remainingBytes == 4) { + track->firstSampleOffset = Read32(data, &offset); + remainingBytes = 0; + } else if (remainingBytes >= 8) { + // Extended FACT format. + offset += 4; + track->firstSampleOffset = Read32(data, &offset); + modifiedSampleOffset = true; + remainingBytes -= 8; + } + break; + } + default: + // Skip the unknown block. + INFO_LOG(Log::Atrac, "Skipping unknown block ID %08x at offset %d with size %d", chunkID, offset - 8, chunkSize); + break; + } + if (remainingBytes) { + INFO_LOG(Log::Atrac, "%08x chunk has %d extra bytes", chunkID, remainingBytes); + } + offset = nextOffset; + } +} diff --git a/Core/Util/AtracTrack.h b/Core/Util/AtracTrack.h index 994258b7b3..5090d61f00 100644 --- a/Core/Util/AtracTrack.h +++ b/Core/Util/AtracTrack.h @@ -106,3 +106,19 @@ struct Track { int AnalyzeAA3Track(const u8 *buffer, u32 size, u32 filesize, Track *track, std::string *error); int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *error); + +struct TrackInfo { + u16 numChans; + u16 blockAlign; + u8 sampleSizeMaybe; + u8 tailFlag; + u8 unused[2]; + u32 dataOff; + u32 endSample; + u32 waveDataSize; + u32 firstSampleOffset; + u32 loopStart; + u32 loopEnd; +}; + +int ParseWaveAT3(const u8 *data, int length, TrackInfo *track); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 03a623b4dc..2cc5bc0575 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1,3 +1,4 @@ + // Copyright (c) 2013- PPSSPP Project. // This program is free software: you can redistribute it and/or modify