mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
300 lines
9.6 KiB
C++
300 lines
9.6 KiB
C++
#include "Common/Log.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Core/Util/AtracTrack.h"
|
|
#include "Core/HLE/ErrorCodes.h"
|
|
#include "Core/MemMap.h"
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
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.
|
|
|
|
// 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 == AT3_PLUS_MAGIC && chunkSize < 52)) {
|
|
*error = "AnalyzeTrack: fmt definition too small(%d)";
|
|
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
|
|
}
|
|
|
|
if (at3fmt->fmtTag == AT3_MAGIC)
|
|
track->codecType = PSP_MODE_AT_3;
|
|
else if (at3fmt->fmtTag == AT3_PLUS_MAGIC)
|
|
track->codecType = PSP_MODE_AT_3_PLUS;
|
|
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 == AT3_MAGIC) {
|
|
// 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);
|
|
|
|
}
|
|
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) {
|
|
if (size < 10) {
|
|
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
|
|
}
|
|
// TODO: Make sure this validation is correct, more testing.
|
|
|
|
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_MODE_AT_3;
|
|
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_MODE_AT_3_PLUS;
|
|
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;
|
|
}
|