Files
ppsspp/Core/Util/AtracTrack.cpp
2025-10-09 12:43:43 -06:00

677 lines
21 KiB
C++

#include "Common/Log.h"
#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
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;
memcpy(&value, buffer + offset, sizeof(u16));
return value;
}
static u32 Read32(const u8 *buffer, int offset) {
u32 value;
memcpy(&value, buffer + offset, sizeof(u32));
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) {
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
}
// If the pointer is bad, let's try to survive, although I'm pretty sure that on a real PSP,
// we crash here.
if (!buffer) {
return SCE_KERNEL_ERROR_INVALID_POINTER;
}
// TODO: Validate stuff more.
if (Read32(buffer, 0) != RIFF_CHUNK_MAGIC) {
ERROR_LOG(Log::ME, "Couldn't find RIFF header");
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
struct RIFFFmtChunk {
u16 fmtTag;
u16 channels;
u32 samplerate;
u32 avgBytesPerSec;
u16 blockAlign;
};
u32 offset = 8;
track->firstSampleOffset = 0;
while (Read32(buffer, offset) != RIFF_WAVE_MAGIC) {
// Get the size preceding the magic.
int chunk = Read32(buffer, offset - 4);
// Round the chunk size up to the nearest 2.
offset += chunk + (chunk & 1);
if (offset + 12 > size) {
*error = StringFromFormat("%d too small for WAVE chunk at offset %d", size, offset);
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
}
if (Read32(buffer, offset) != RIFF_CHUNK_MAGIC) {
*error = "RIFF chunk did not contain WAVE";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
offset += 8;
}
offset += 4;
if (offset != 12) {
WARN_LOG(Log::ME, "RIFF chunk at offset: %d", offset);
}
// RIFF size excluding chunk header.
track->fileSize = Read32(buffer, offset - 8) + 8;
// Even if the RIFF size is too low, it may simply be incorrect. This works on real firmware.
u32 maxSize = std::max(track->fileSize, size);
bool bfoundData = false;
u32 dataChunkSize = 0;
int sampleOffsetAdjust = 0;
while (maxSize >= offset + 8 && !bfoundData) {
int chunkMagic = Read32(buffer, offset);
u32 chunkSize = Read32(buffer, offset + 4);
// Account for odd sized chunks.
if (chunkSize & 1) {
WARN_LOG(Log::ME, "RIFF chunk had uneven size");
}
chunkSize += (chunkSize & 1);
offset += 8;
if (chunkSize > maxSize - offset)
break;
switch (chunkMagic) {
case FMT_CHUNK_MAGIC:
{
if (track->codecType != 0) {
*error = "AnalyzeTrack: multiple fmt chunks is not valid";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
auto at3fmt = (const RIFFFmtChunk *)(buffer + offset);
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 == WAVE_FORMAT_AT3)
track->codecType = PSP_CODEC_AT3;
else if (at3fmt->fmtTag == WAVE_FORMAT_EXTENSIBLE)
track->codecType = PSP_CODEC_AT3PLUS;
else {
*error = "AnalyzeTrack: invalid fmt magic: %04x";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
track->channels = at3fmt->channels;
if (track->channels != 1 && track->channels != 2) {
*error = "AnalyzeTrack: unsupported channel count %d";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
if (at3fmt->samplerate != 44100) {
*error = "AnalyzeTrack: unsupported sample rate %d";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
track->bitrate = at3fmt->avgBytesPerSec * 8;
track->bytesPerFrame = at3fmt->blockAlign;
if (track->bytesPerFrame == 0) {
*error = "invalid bytes per frame: %d";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
// TODO: There are some format specific bytes here which seem to have fixed values?
// Probably don't need them.
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 == WAVE_FORMAT_EXTENSIBLE) {
// It's in an "Extensible" wave format. Let's read some more.
}
if (chunkSize > 16) {
// Read and format extra bytes as hexadecimal
std::string hex;
DataToHexString(buffer + offset + 16, chunkSize - 16, &hex, false);
DEBUG_LOG(Log::ME, "Additional chunk data (beyond 16 bytes): %s", hex.c_str());
}
break;
}
case FACT_CHUNK_MAGIC:
{
track->endSample = Read32(buffer, offset);
if (chunkSize >= 8) {
track->firstSampleOffset = Read32(buffer, offset + 4);
}
if (chunkSize >= 12) {
u32 largerOffset = Read32(buffer, offset + 8);
// Works, but "largerOffset"??
sampleOffsetAdjust = track->firstSampleOffset - largerOffset;
}
break;
}
case SMPL_CHUNK_MAGIC:
{
if (chunkSize < 32) {
*error = StringFromFormat("smpl chunk too small (%d)", chunkSize);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
int checkNumLoops = Read32(buffer, offset + 28);
if (checkNumLoops != 0 && chunkSize < 36 + 20) {
*error = StringFromFormat("smpl chunk too small for loop (%d, %d)", checkNumLoops, chunkSize);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
if (checkNumLoops < 0) {
*error = StringFromFormat("bad checkNumLoops (%d)", checkNumLoops);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
track->loopinfo.resize(checkNumLoops);
u32 loopinfoOffset = offset + 36;
// The PSP only cares about the first loop start and end, it seems.
// Most likely can skip the rest of this data, but it's not hurting anyone.
for (int i = 0; i < checkNumLoops && 36 + (u32)i < chunkSize; i++, loopinfoOffset += 24) {
track->loopinfo[i].cuePointID = Read32(buffer, loopinfoOffset + 0);
track->loopinfo[i].type = Read32(buffer, loopinfoOffset + 4);
track->loopinfo[i].startSample = Read32(buffer, loopinfoOffset + 8);
track->loopinfo[i].endSample = Read32(buffer, loopinfoOffset + 12);
track->loopinfo[i].fraction = Read32(buffer, loopinfoOffset + 16);
track->loopinfo[i].playCount = Read32(buffer, loopinfoOffset + 20);
if (i == 0 && track->loopinfo[i].startSample >= track->loopinfo[i].endSample) {
*error = "AnalyzeTrack: loop starts after it ends";
return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
}
}
break;
}
case DATA_CHUNK_MAGIC:
{
bfoundData = true;
track->dataByteOffset = offset;
dataChunkSize = chunkSize;
if (track->fileSize < offset + chunkSize) {
WARN_LOG(Log::ME, "Atrac data chunk extends beyond riff chunk");
track->fileSize = offset + chunkSize;
}
}
break;
}
offset += chunkSize;
}
if (track->codecType == 0) {
*error = "Could not detect codec";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
if (!bfoundData) {
*error = "AnalyzeTrack: No data chunk found";
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
}
// set the loopStartSample_ and loopEndSample_ by loopinfo_
if (track->loopinfo.size() > 0) {
track->loopStartSample = track->loopinfo[0].startSample + track->FirstOffsetExtra() + sampleOffsetAdjust;
track->loopEndSample = track->loopinfo[0].endSample + track->FirstOffsetExtra() + sampleOffsetAdjust;
} else {
track->loopStartSample = -1;
track->loopEndSample = -1;
}
// if there is no correct endsample, try to guess it
if (track->endSample <= 0 && track->bytesPerFrame != 0) {
track->endSample = (dataChunkSize / track->bytesPerFrame) * track->SamplesPerFrame();
track->endSample -= track->FirstSampleOffsetFull();
}
track->endSample -= 1;
if (track->loopEndSample != -1 && track->loopEndSample > track->endSample + track->FirstSampleOffsetFull()) {
*error = "AnalyzeTrack: loop after end of data";
return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
}
return 0;
}
int AnalyzeAA3Track(const u8 *buffer, u32 size, u32 fileSize, Track *track, std::string *error) {
// TODO: Make sure this validation is correct, more testing.
if (size < 10) {
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
}
// If the pointer is bad, let's try to survive, although I'm pretty sure that on a real PSP,
// we crash here.
if (!buffer) {
_dbg_assert_(false);
return SCE_KERNEL_ERROR_INVALID_POINTER;
}
if (buffer[0] != 'e' || buffer[1] != 'a' || buffer[2] != '3') {
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
// It starts with an id3 header (replaced with ea3.) This is the size.
u32 tagSize = buffer[9] | (buffer[8] << 7) | (buffer[7] << 14) | (buffer[6] << 21);
if (size < tagSize + 36) {
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
}
// EA3 header starts at id3 header (10) + tagSize.
buffer = buffer + 10 + tagSize;
if (buffer[0] != 'E' || buffer[1] != 'A' || buffer[2] != '3') {
ERROR_LOG(Log::ME, "AnalyzeAA3Track: Invalid EA3 magic bytes");
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
track->fileSize = fileSize;
// Based on FFmpeg's code.
u32 codecParams = buffer[33] | (buffer[34] << 8) | (buffer[35] << 16);
const u32 at3SampleRates[8] = { 32000, 44100, 48000, 88200, 96000, 0 };
switch (buffer[32]) {
case 0:
track->codecType = PSP_CODEC_AT3;
track->bytesPerFrame = (codecParams & 0x03FF) * 8;
track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 1024;
track->channels = 2;
track->jointStereo = (codecParams >> 17) & 1;
break;
case 1:
track->codecType = PSP_CODEC_AT3PLUS;
track->bytesPerFrame = ((codecParams & 0x03FF) * 8) + 8;
track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 2048;
track->channels = (codecParams >> 10) & 7;
break;
case 3:
case 4:
case 5:
ERROR_LOG(Log::ME, "AnalyzeAA3Track: unsupported codec type %d", buffer[32]);
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
default:
ERROR_LOG(Log::ME, "AnalyzeAA3Track: invalid codec type %d", buffer[32]);
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
track->dataByteOffset = 10 + tagSize + 96;
track->firstSampleOffset = 0;
if (track->endSample < 0 && track->bytesPerFrame != 0) {
track->endSample = ((track->fileSize - track->dataByteOffset) / track->bytesPerFrame) * track->SamplesPerFrame();
}
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) {
_assert_(data != nullptr);
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; // u8 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;
}
}
struct AA3Info {
int tagSize;
int dataOff;
int extraBytes;
u8 codecType; // 1 == at3plus
u32 codecParams;
u32 waveDataSize;
};
static u16 ParseU16BE(const u8 *data, int *offset) {
const u16 value = (data[*offset] << 8) | data[*offset + 1];
*offset += 2;
return value;
}
static u32 Parse3BytesBE(const u8 *data, int *offset) {
const u32 id = (data[*offset] << 16) | (data[*offset + 1] << 8) | data[*offset + 2];
*offset += 3;
return id;
}
static u32 Parse28BitIntBE(const u8 *data, int *offset) {
const u32 value = ((data[*offset] & 0x7F) << 21) | ((data[*offset + 1] & 0x7F) << 14) | ((data[*offset + 2] & 0x7F) << 7) | (data[*offset + 3] & 0x7F);
*offset += 4;
return value;
}
static u32 Parse4BytesBE(const u8 *data, int *offset) {
const u32 value = (data[*offset] << 24) | (data[*offset + 1] << 16) | (data[*offset + 2] << 8) | data[*offset + 3];
*offset += 4;
return value;
}
static u32 ParseAA3Headers(int readSize, AA3Info *info, int fileSize, const u8 *aa3Data) {
if ((u32)readSize < 9) {
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
}
int offset = 0;
const u32 id = Parse3BytesBE(aa3Data, &offset);
if (id == 0x656133 || id == 0x494433) { // "ea3" or "id3"
const u8 *dataPtr = aa3Data + offset;
if ((*dataPtr != 3) || (offset = offset + 3, dataPtr[1] != 0)) {
return SCE_ERROR_ATRAC_AA3_OTHER_FAILURE;
}
info->tagSize = Parse28BitIntBE(aa3Data, &offset);
if (info->tagSize == 0xffffffff) {
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
offset += info->tagSize;
if (offset >= readSize) {
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
}
if (aa3Data[offset] == 0) {
offset += 0x10;
}
info->dataOff = offset;
} else {
// Might be that the file just starts with the EA3 header.
info->tagSize = 0;
info->dataOff = 0;
offset = 0;
}
if (offset + 0x22 > readSize) {
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
}
if (Parse3BytesBE(aa3Data, &offset) != 0x454133) {
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
offset++;
info->extraBytes = ParseU16BE(aa3Data, &offset);;
if (ParseU16BE(aa3Data, &offset) != 0xffff) {
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
offset += 24;
info->codecType = aa3Data[offset];
u32 codec;
switch (info->codecType) {
case 0: codec = PSP_CODEC_AT3; break;
case 1: codec = PSP_CODEC_AT3PLUS; break;
default: return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
info->codecParams = Parse4BytesBE(aa3Data, &offset);
const u32 dataOff = info->dataOff + info->extraBytes;
info->waveDataSize = fileSize - dataOff;
info->dataOff = dataOff;
return codec;
}
static int ConvertAA3InfoToTrackInfo(const AA3Info *in, TrackInfo *out) {
out->loopEnd = 0xffffffff;
out->loopStart = 0xffffffff;
out->waveDataSize = in->waveDataSize;
out->dataOff = in->dataOff;
out->numChans = 2;
out->endSample = 0;
const u32 codecParams = in->codecParams;
if (in->codecType == 0) {
if ((codecParams & 0xe000) != 0x2000) {
return SCE_ERROR_ATRAC_AA3_BAD_CODEC_PARAMS;
}
out->firstSampleOffset = 0x400;
out->blockAlign = (u16)((codecParams & 0x3ff) << 3);
if ((codecParams & 0x20000) == 0) {
out->sampleSizeMaybe = 0;
} else {
out->sampleSizeMaybe = 1;
}
out->tailFlag = 0;
return PSP_CODEC_AT3;
} else if (in->codecType == 1) {
if ((codecParams & 0x1c00) != 0x800 || (codecParams & 0xe000) != 0x2000) {
return SCE_ERROR_ATRAC_AA3_BAD_CODEC_PARAMS;
}
out->firstSampleOffset = 0x800;
out->blockAlign = ((u16)codecParams & 0x3ff) * 8 + 8;
out->sampleSizeMaybe = (u8)(((codecParams >> 8) & 0x3) | 0x28);
out->tailFlag = codecParams & 0xFF;
return PSP_CODEC_AT3PLUS;
} else {
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
}
int ParseAA3(const u8 *buffer, int readSize, int fileSize, TrackInfo *track) {
AA3Info info;
int retval = ParseAA3Headers(readSize, &info, fileSize, buffer);
if (retval < 0) {
return retval;
}
return ConvertAA3InfoToTrackInfo(&info, track);
}