mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Reimplement wave parsing more simply, for AtracCtx2.
This commit is contained in:
1 parent
5c77ff9147
commit
0fc7d430b4
7 files changed
+429
-135
No files matched your search
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
// ------------------------------------------------
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,3 +1,4 @@
|
||||
|
||||
// Copyright (c) 2013- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
|
||||
Reference in new issue
Block a user