Reimplement wave parsing more simply, for AtracCtx2.

This commit is contained in:
Henrik Rydgård committed 2025-06-26 11:06:18 +02:00
1 parent 5c77ff9147
commit 0fc7d430b4
7 files changed
+429 -135

No files matched your search

+9
View File
@@ -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.
+163 -118
View File
@@ -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 &params, int codecType) {
+8 -1
View File
@@ -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)
// ------------------------------------------------
+2 -2
View File
@@ -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;
};
+230 -14
View File
@@ -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;
}
}
+16
View File
@@ -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);
+1
View File
@@ -1,3 +1,4 @@
// Copyright (c) 2013- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify