mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Break out AtracTrack into its own file. Add a little atrac analysis tool to ImDebugger (for future use)
This commit is contained in:
parent
24d859f773
commit
e70dd3b2df
21 changed files with 571 additions and 443 deletions
|
@ -2413,6 +2413,8 @@ add_library(${CoreLibName} ${CoreLinkType}
|
||||||
Core/System.h
|
Core/System.h
|
||||||
Core/ThreadPools.cpp
|
Core/ThreadPools.cpp
|
||||||
Core/ThreadPools.h
|
Core/ThreadPools.h
|
||||||
|
Core/Util/AtracTrack.cpp
|
||||||
|
Core/Util/AtracTrack.h
|
||||||
Core/Util/AudioFormat.cpp
|
Core/Util/AudioFormat.cpp
|
||||||
Core/Util/AudioFormat.h
|
Core/Util/AudioFormat.h
|
||||||
Core/Util/GameManager.cpp
|
Core/Util/GameManager.cpp
|
||||||
|
|
|
@ -104,6 +104,7 @@ enum class BrowseFileType {
|
||||||
SOUND_EFFECT,
|
SOUND_EFFECT,
|
||||||
ZIP,
|
ZIP,
|
||||||
SYMBOL_MAP,
|
SYMBOL_MAP,
|
||||||
|
ATRAC3,
|
||||||
ANY,
|
ANY,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1086,6 +1086,7 @@
|
||||||
<ClCompile Include="System.cpp" />
|
<ClCompile Include="System.cpp" />
|
||||||
<ClCompile Include="ThreadPools.cpp" />
|
<ClCompile Include="ThreadPools.cpp" />
|
||||||
<ClCompile Include="TiltEventProcessor.cpp" />
|
<ClCompile Include="TiltEventProcessor.cpp" />
|
||||||
|
<ClCompile Include="Util\AtracTrack.cpp" />
|
||||||
<ClCompile Include="Util\AudioFormat.cpp" />
|
<ClCompile Include="Util\AudioFormat.cpp" />
|
||||||
<ClCompile Include="Util\BlockAllocator.cpp" />
|
<ClCompile Include="Util\BlockAllocator.cpp" />
|
||||||
<ClCompile Include="Util\DisArm64.cpp" />
|
<ClCompile Include="Util\DisArm64.cpp" />
|
||||||
|
@ -1471,6 +1472,7 @@
|
||||||
<ClInclude Include="ThreadEventQueue.h" />
|
<ClInclude Include="ThreadEventQueue.h" />
|
||||||
<ClInclude Include="ThreadPools.h" />
|
<ClInclude Include="ThreadPools.h" />
|
||||||
<ClInclude Include="TiltEventProcessor.h" />
|
<ClInclude Include="TiltEventProcessor.h" />
|
||||||
|
<ClInclude Include="Util\AtracTrack.h" />
|
||||||
<ClInclude Include="Util\AudioFormat.h" />
|
<ClInclude Include="Util\AudioFormat.h" />
|
||||||
<ClInclude Include="Util\BlockAllocator.h" />
|
<ClInclude Include="Util\BlockAllocator.h" />
|
||||||
<ClInclude Include="Util\DisArm64.h" />
|
<ClInclude Include="Util\DisArm64.h" />
|
||||||
|
|
|
@ -1342,6 +1342,9 @@
|
||||||
<ClCompile Include="HLE\sceAac.cpp">
|
<ClCompile Include="HLE\sceAac.cpp">
|
||||||
<Filter>HLE\Libraries</Filter>
|
<Filter>HLE\Libraries</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="Util\AtracTrack.cpp">
|
||||||
|
<Filter>Util</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="ELF\ElfReader.h">
|
<ClInclude Include="ELF\ElfReader.h">
|
||||||
|
@ -2166,6 +2169,9 @@
|
||||||
<ClInclude Include="HLE\ErrorCodes.h">
|
<ClInclude Include="HLE\ErrorCodes.h">
|
||||||
<Filter>HLE</Filter>
|
<Filter>HLE</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="Util\AtracTrack.h">
|
||||||
|
<Filter>Util</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\LICENSE.TXT" />
|
<None Include="..\LICENSE.TXT" />
|
||||||
|
|
|
@ -32,13 +32,6 @@
|
||||||
|
|
||||||
const size_t overAllocBytes = 16384;
|
const size_t overAllocBytes = 16384;
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
Atrac::~Atrac() {
|
Atrac::~Atrac() {
|
||||||
ResetData();
|
ResetData();
|
||||||
}
|
}
|
||||||
|
@ -238,284 +231,6 @@ void Track::DebugLog() const {
|
||||||
DEBUG_LOG(Log::ME, "sampleSize: %d (%03x", bytesPerFrame, bytesPerFrame);
|
DEBUG_LOG(Log::ME, "sampleSize: %d (%03x", bytesPerFrame, bytesPerFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
int AnalyzeAtracTrack(u32 addr, u32 size, Track *track) {
|
|
||||||
// 72 is about the size of the minimum required data to even be valid.
|
|
||||||
|
|
||||||
// TODO: Validate stuff more.
|
|
||||||
if (Memory::ReadUnchecked_U32(addr) != RIFF_CHUNK_MAGIC) {
|
|
||||||
ERROR_LOG(Log::ME, "Couldn't find RIFF header");
|
|
||||||
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RIFFFmtChunk {
|
|
||||||
u16_le fmtTag;
|
|
||||||
u16_le channels;
|
|
||||||
u32_le samplerate;
|
|
||||||
u32_le avgBytesPerSec;
|
|
||||||
u16_le blockAlign;
|
|
||||||
};
|
|
||||||
|
|
||||||
u32 offset = 8;
|
|
||||||
track->firstSampleOffset = 0;
|
|
||||||
|
|
||||||
while (Memory::Read_U32(addr + offset) != RIFF_WAVE_MAGIC) {
|
|
||||||
// Get the size preceding the magic.
|
|
||||||
int chunk = Memory::Read_U32(addr + offset - 4);
|
|
||||||
// Round the chunk size up to the nearest 2.
|
|
||||||
offset += chunk + (chunk & 1);
|
|
||||||
if (offset + 12 > size) {
|
|
||||||
ERROR_LOG(Log::ME, "AnalyzeAtracTrack(%08x, %d): too small for WAVE chunk at offset %d", addr, size, offset);
|
|
||||||
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
|
|
||||||
}
|
|
||||||
if (Memory::Read_U32(addr + offset) != RIFF_CHUNK_MAGIC) {
|
|
||||||
ERROR_LOG(Log::ME, "AnalyzeAtracTrack(%08x, %d): RIFF chunk did not contain WAVE", addr, size);
|
|
||||||
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 = Memory::Read_U32(addr + 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 = Memory::Read_U32(addr + offset);
|
|
||||||
u32 chunkSize = Memory::Read_U32(addr + offset + 4);
|
|
||||||
// Account for odd sized chunks.
|
|
||||||
if (chunkSize & 1) {
|
|
||||||
WARN_LOG_REPORT_ONCE(oddchunk, 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_LOG(Log::ME, "AnalyzeTrack: multiple fmt chunks is not valid");
|
|
||||||
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto at3fmt = PSPPointer<const RIFFFmtChunk>::Create(addr + offset);
|
|
||||||
if (chunkSize < 32 || (at3fmt->fmtTag == AT3_PLUS_MAGIC && chunkSize < 52)) {
|
|
||||||
ERROR_LOG(Log::ME, "AnalyzeTrack: fmt definition too small(%d)", chunkSize);
|
|
||||||
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_LOG(Log::ME, "AnalyzeTrack: invalid fmt magic: %04x", at3fmt->fmtTag);
|
|
||||||
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
|
|
||||||
}
|
|
||||||
track->channels = at3fmt->channels;
|
|
||||||
if (track->channels != 1 && track->channels != 2) {
|
|
||||||
ERROR_LOG_REPORT(Log::ME, "AnalyzeTrack: unsupported channel count %d", track->channels);
|
|
||||||
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
|
|
||||||
}
|
|
||||||
if (at3fmt->samplerate != 44100) {
|
|
||||||
ERROR_LOG_REPORT(Log::ME, "AnalyzeTrack: unsupported sample rate %d", at3fmt->samplerate);
|
|
||||||
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
|
|
||||||
}
|
|
||||||
track->bitrate = at3fmt->avgBytesPerSec * 8;
|
|
||||||
track->bytesPerFrame = at3fmt->blockAlign;
|
|
||||||
if (track->bytesPerFrame == 0) {
|
|
||||||
ERROR_LOG_REPORT(Log::ME, "invalid bytes per frame: %d", track->bytesPerFrame);
|
|
||||||
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 = Memory::Read_U16(addr + offset + 24);
|
|
||||||
// Then there are more fields here.
|
|
||||||
u16 unknown1_2 = Memory::Read_U16(addr + offset + 30);
|
|
||||||
|
|
||||||
}
|
|
||||||
if (chunkSize > 16) {
|
|
||||||
// Read and format extra bytes as hexadecimal
|
|
||||||
std::string hex;
|
|
||||||
DataToHexString(Memory::GetPointer(addr + 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 = Memory::Read_U32(addr + offset);
|
|
||||||
if (chunkSize >= 8) {
|
|
||||||
track->firstSampleOffset = Memory::Read_U32(addr + offset + 4);
|
|
||||||
}
|
|
||||||
if (chunkSize >= 12) {
|
|
||||||
u32 largerOffset = Memory::Read_U32(addr + offset + 8);
|
|
||||||
// Works, but "largerOffset"??
|
|
||||||
sampleOffsetAdjust = track->firstSampleOffset - largerOffset;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SMPL_CHUNK_MAGIC:
|
|
||||||
{
|
|
||||||
if (chunkSize < 32) {
|
|
||||||
ERROR_LOG(Log::ME, "AnalyzeTrack: smpl chunk too small (%d)", chunkSize);
|
|
||||||
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
|
|
||||||
}
|
|
||||||
int checkNumLoops = Memory::Read_U32(addr + offset + 28);
|
|
||||||
if (checkNumLoops != 0 && chunkSize < 36 + 20) {
|
|
||||||
ERROR_LOG(Log::ME, "AnalyzeTrack: smpl chunk too small for loop (%d, %d)", checkNumLoops, chunkSize);
|
|
||||||
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
|
|
||||||
}
|
|
||||||
if (checkNumLoops < 0) {
|
|
||||||
ERROR_LOG(Log::ME, "bad checkNumLoops (%d)", checkNumLoops);
|
|
||||||
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
track->loopinfo.resize(checkNumLoops);
|
|
||||||
u32 loopinfoAddr = addr + 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++, loopinfoAddr += 24) {
|
|
||||||
track->loopinfo[i].cuePointID = Memory::Read_U32(loopinfoAddr);
|
|
||||||
track->loopinfo[i].type = Memory::Read_U32(loopinfoAddr + 4);
|
|
||||||
track->loopinfo[i].startSample = Memory::Read_U32(loopinfoAddr + 8);
|
|
||||||
track->loopinfo[i].endSample = Memory::Read_U32(loopinfoAddr + 12);
|
|
||||||
track->loopinfo[i].fraction = Memory::Read_U32(loopinfoAddr + 16);
|
|
||||||
track->loopinfo[i].playCount = Memory::Read_U32(loopinfoAddr + 20);
|
|
||||||
|
|
||||||
if (i == 0 && track->loopinfo[i].startSample >= track->loopinfo[i].endSample) {
|
|
||||||
ERROR_LOG(Log::ME, "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_REPORT(Log::ME, "Atrac data chunk extends beyond riff chunk");
|
|
||||||
track->fileSize = offset + chunkSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
offset += chunkSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (track->codecType == 0) {
|
|
||||||
ERROR_LOG(Log::ME, "AnalyzeTrack: Could not detect codec");
|
|
||||||
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bfoundData) {
|
|
||||||
ERROR_LOG(Log::ME, "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_LOG(Log::ME, "AnalyzeTrack: loop after end of data");
|
|
||||||
return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AnalyzeAA3Track(u32 addr, u32 size, u32 fileSize, Track *track) {
|
|
||||||
if (size < 10) {
|
|
||||||
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
|
|
||||||
}
|
|
||||||
// TODO: Make sure this validation is correct, more testing.
|
|
||||||
|
|
||||||
const u8 *buffer = Memory::GetPointer(addr);
|
|
||||||
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 = Memory::GetPointer(addr + 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Atrac::GetSoundSample(int *endSample, int *loopStartSample, int *loopEndSample) const {
|
int Atrac::GetSoundSample(int *endSample, int *loopStartSample, int *loopEndSample) const {
|
||||||
*endSample = track_.endSample;
|
*endSample = track_.endSample;
|
||||||
*loopStartSample = track_.loopStartSample == -1 ? -1 : track_.loopStartSample - track_.FirstSampleOffsetFull();
|
*loopStartSample = track_.loopStartSample == -1 ? -1 : track_.loopStartSample - track_.FirstSampleOffsetFull();
|
||||||
|
|
|
@ -37,10 +37,6 @@ struct AtracResetBufferInfo {
|
||||||
AtracSingleResetBufferInfo second;
|
AtracSingleResetBufferInfo second;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define AT3_MAGIC 0x0270
|
|
||||||
#define AT3_PLUS_MAGIC 0xFFFE
|
|
||||||
#define PSP_MODE_AT_3_PLUS 0x00001000
|
|
||||||
#define PSP_MODE_AT_3 0x00001001
|
|
||||||
|
|
||||||
const int PSP_ATRAC_ALLDATA_IS_ON_MEMORY = -1;
|
const int PSP_ATRAC_ALLDATA_IS_ON_MEMORY = -1;
|
||||||
const int PSP_ATRAC_NONLOOP_STREAM_DATA_IS_ON_MEMORY = -2;
|
const int PSP_ATRAC_NONLOOP_STREAM_DATA_IS_ON_MEMORY = -2;
|
||||||
|
@ -66,107 +62,8 @@ struct InputBuffer {
|
||||||
u32 fileoffset;
|
u32 fileoffset;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AtracLoopInfo {
|
|
||||||
int cuePointID;
|
|
||||||
int type;
|
|
||||||
int startSample;
|
|
||||||
int endSample;
|
|
||||||
int fraction;
|
|
||||||
int playCount;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AudioDecoder;
|
class AudioDecoder;
|
||||||
|
|
||||||
// This is (mostly) constant info, once a track has been loaded.
|
|
||||||
struct Track {
|
|
||||||
// This both does and doesn't belong in Track - it's fixed for an Atrac instance. Oh well.
|
|
||||||
u32 codecType = 0;
|
|
||||||
|
|
||||||
// Size of the full track being streamed or played. Can be a lot larger than the in-memory buffer in the streaming modes.
|
|
||||||
u32 fileSize = 0;
|
|
||||||
|
|
||||||
// Not really used for much except queries, this keeps track of the bitrate of the track (kbps).
|
|
||||||
u32 bitrate = 64;
|
|
||||||
|
|
||||||
// Signifies whether to use a more efficient coding mode with less stereo separation. For our purposes, just metadata,
|
|
||||||
// not actually used in decoding.
|
|
||||||
int jointStereo = 0;
|
|
||||||
|
|
||||||
// Number of audio channels in the track.
|
|
||||||
u16 channels = 2;
|
|
||||||
|
|
||||||
// The size of an encoded frame in bytes.
|
|
||||||
u16 bytesPerFrame = 0;
|
|
||||||
|
|
||||||
// Byte offset of the first encoded frame in the input buffer. Note: Some samples may be skipped according to firstSampleOffset.
|
|
||||||
int dataByteOffset = 0;
|
|
||||||
|
|
||||||
// How many samples to skip from the beginning of a track when decoding.
|
|
||||||
// Actually, the real number is this added to FirstOffsetExtra(codecType). You can call
|
|
||||||
// FirstSampleOffset2() to get that.
|
|
||||||
// Some use of these offsets around the code seem to be inconsistent, sometimes the extra is included,
|
|
||||||
// sometimes not.
|
|
||||||
int firstSampleOffset = 0;
|
|
||||||
|
|
||||||
// Last sample number. Inclusive. Though, we made it so that in Analyze, it's exclusive in the file.
|
|
||||||
// Does not take firstSampleOffset into account.
|
|
||||||
int endSample = -1;
|
|
||||||
|
|
||||||
// NOTE: The below CAN be written.
|
|
||||||
// Loop configuration. The PSP only supports one loop but we store them all.
|
|
||||||
std::vector<AtracLoopInfo> loopinfo;
|
|
||||||
// The actual used loop offsets. These appear to be raw offsets, not taking FirstSampleOffset2() into account.
|
|
||||||
int loopStartSample = -1;
|
|
||||||
int loopEndSample = -1;
|
|
||||||
|
|
||||||
// Input frame size
|
|
||||||
int BytesPerFrame() const {
|
|
||||||
return bytesPerFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int FirstOffsetExtra() const {
|
|
||||||
// These first samples are skipped, after first possibly skipping 0-2 full frames, it seems.
|
|
||||||
return codecType == PSP_MODE_AT_3_PLUS ? 0x170 : 0x45;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Includes the extra offset. See firstSampleOffset comment above.
|
|
||||||
int FirstSampleOffsetFull() const {
|
|
||||||
return FirstOffsetExtra() + firstSampleOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output frame size, different between the two supported codecs.
|
|
||||||
int SamplesPerFrame() const {
|
|
||||||
return codecType == PSP_MODE_AT_3_PLUS ? ATRAC3PLUS_MAX_SAMPLES : ATRAC3_MAX_SAMPLES;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Bitrate() const {
|
|
||||||
int bitrate = (bytesPerFrame * 352800) / 1000;
|
|
||||||
if (codecType == PSP_MODE_AT_3_PLUS)
|
|
||||||
bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0;
|
|
||||||
else
|
|
||||||
bitrate = (bitrate + 511) >> 10;
|
|
||||||
return bitrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This appears to be buggy, should probably include FirstOffsetExtra?
|
|
||||||
// Actually the units don't even make sense here.
|
|
||||||
int DecodePosBySample(int sample) const {
|
|
||||||
return (u32)(firstSampleOffset + sample / (int)SamplesPerFrame() * bytesPerFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This appears to be buggy, should probably include FirstOffsetExtra?
|
|
||||||
int FileOffsetBySample(int sample) const {
|
|
||||||
int offsetSample = sample + firstSampleOffset;
|
|
||||||
int frameOffset = offsetSample / (int)SamplesPerFrame();
|
|
||||||
return (u32)(dataByteOffset + bytesPerFrame + frameOffset * bytesPerFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DebugLog() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
int AnalyzeAA3Track(u32 addr, u32 size, u32 filesize, Track *track);
|
|
||||||
int AnalyzeAtracTrack(u32 addr, u32 size, Track *track);
|
|
||||||
|
|
||||||
class AtracBase {
|
class AtracBase {
|
||||||
public:
|
public:
|
||||||
virtual ~AtracBase();
|
virtual ~AtracBase();
|
||||||
|
|
|
@ -503,7 +503,7 @@ u32 Atrac2::AddStreamDataSas(u32 bufPtr, u32 bytesToAdd) {
|
||||||
// Sol Trigger is the only game I know that uses this.
|
// Sol Trigger is the only game I know that uses this.
|
||||||
_dbg_assert_(false);
|
_dbg_assert_(false);
|
||||||
|
|
||||||
u8 *dest = Memory::GetPointerWrite(info.buffer);
|
u8 *dest = Memory::GetPointerWrite(sasBasePtr_ + sasReadOffset_);
|
||||||
memcpy(dest, Memory::GetPointer(bufPtr), bytesToAdd);
|
memcpy(dest, Memory::GetPointer(bufPtr), bytesToAdd);
|
||||||
info.buffer += bytesToAdd;
|
info.buffer += bytesToAdd;
|
||||||
info.streamDataByte += bytesToAdd;
|
info.streamDataByte += bytesToAdd;
|
||||||
|
|
|
@ -635,9 +635,10 @@ static u32 sceAtracSetHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u32 b
|
||||||
}
|
}
|
||||||
|
|
||||||
Track track;
|
Track track;
|
||||||
int ret = AnalyzeAtracTrack(buffer, readSize, &track);
|
std::string error;
|
||||||
|
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return hleLogError(Log::ME, ret);
|
return hleLogError(Log::ME, ret, "%s", error.c_str());
|
||||||
}
|
}
|
||||||
if (track.codecType != atracContextTypes[atracID]) {
|
if (track.codecType != atracContextTypes[atracID]) {
|
||||||
// TODO: Should this not change the buffer size?
|
// TODO: Should this not change the buffer size?
|
||||||
|
@ -670,9 +671,10 @@ static u32 sceAtracSetData(int atracID, u32 buffer, u32 bufferSize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Track track;
|
Track track;
|
||||||
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
|
std::string error;
|
||||||
|
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return hleLogError(Log::ME, ret);
|
return hleLogError(Log::ME, ret, "%s", error.c_str());
|
||||||
}
|
}
|
||||||
if (track.codecType != atracContextTypes[atracID]) {
|
if (track.codecType != atracContextTypes[atracID]) {
|
||||||
// TODO: Should this not change the buffer size?
|
// TODO: Should this not change the buffer size?
|
||||||
|
@ -697,9 +699,10 @@ static int sceAtracSetDataAndGetID(u32 buffer, int bufferSize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Track track;
|
Track track;
|
||||||
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
|
std::string error;
|
||||||
|
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return hleLogError(Log::ME, ret);
|
return hleLogError(Log::ME, ret, "%s", error.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
int atracID = AllocAndRegisterAtrac(track.codecType);
|
int atracID = AllocAndRegisterAtrac(track.codecType);
|
||||||
|
@ -722,9 +725,10 @@ static int sceAtracSetHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
Track track;
|
Track track;
|
||||||
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
|
std::string error;
|
||||||
|
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return hleLogError(Log::ME, ret);
|
return hleLogError(Log::ME, ret, "%s", error.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
int atracID = AllocAndRegisterAtrac(track.codecType);
|
int atracID = AllocAndRegisterAtrac(track.codecType);
|
||||||
|
@ -841,9 +845,10 @@ static int sceAtracSetMOutHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u
|
||||||
}
|
}
|
||||||
|
|
||||||
Track track;
|
Track track;
|
||||||
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
|
std::string error;
|
||||||
|
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return hleLogError(Log::ME, ret);
|
return hleLogError(Log::ME, ret, "%s", error.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = atrac->SetData(track, buffer, readSize, bufferSize, 1);
|
ret = atrac->SetData(track, buffer, readSize, bufferSize, 1);
|
||||||
|
@ -865,9 +870,10 @@ static u32 sceAtracSetMOutData(int atracID, u32 buffer, u32 bufferSize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Track track;
|
Track track;
|
||||||
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
|
std::string error;
|
||||||
|
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return hleLogError(Log::ME, ret);
|
return hleLogError(Log::ME, ret, "%s", error.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = atrac->SetData(track, buffer, bufferSize, bufferSize, 1);
|
ret = atrac->SetData(track, buffer, bufferSize, bufferSize, 1);
|
||||||
|
@ -883,10 +889,12 @@ static u32 sceAtracSetMOutData(int atracID, u32 buffer, u32 bufferSize) {
|
||||||
// See note in above function.
|
// See note in above function.
|
||||||
static int sceAtracSetMOutDataAndGetID(u32 buffer, u32 bufferSize) {
|
static int sceAtracSetMOutDataAndGetID(u32 buffer, u32 bufferSize) {
|
||||||
Track track;
|
Track track;
|
||||||
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
|
std::string error;
|
||||||
|
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return hleLogError(Log::ME, ret);
|
return hleLogError(Log::ME, ret, "%s", error.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.channels != 1) {
|
if (track.channels != 1) {
|
||||||
return hleReportError(Log::ME, SCE_ERROR_ATRAC_NOT_MONO, "not mono data");
|
return hleReportError(Log::ME, SCE_ERROR_ATRAC_NOT_MONO, "not mono data");
|
||||||
}
|
}
|
||||||
|
@ -910,10 +918,12 @@ static int sceAtracSetMOutHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bu
|
||||||
}
|
}
|
||||||
|
|
||||||
Track track;
|
Track track;
|
||||||
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
|
std::string error;
|
||||||
|
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return hleLogError(Log::ME, ret);
|
return hleLogError(Log::ME, ret, "%s", error.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.channels != 1) {
|
if (track.channels != 1) {
|
||||||
return hleReportError(Log::ME, SCE_ERROR_ATRAC_NOT_MONO, "not mono data");
|
return hleReportError(Log::ME, SCE_ERROR_ATRAC_NOT_MONO, "not mono data");
|
||||||
}
|
}
|
||||||
|
@ -933,9 +943,10 @@ static int sceAtracSetMOutHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bu
|
||||||
|
|
||||||
static int sceAtracSetAA3DataAndGetID(u32 buffer, u32 bufferSize, u32 fileSize, u32 metadataSizeAddr) {
|
static int sceAtracSetAA3DataAndGetID(u32 buffer, u32 bufferSize, u32 fileSize, u32 metadataSizeAddr) {
|
||||||
Track track;
|
Track track;
|
||||||
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
|
std::string error;
|
||||||
|
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return hleLogError(Log::ME, ret);
|
return hleLogError(Log::ME, ret, "%s", error.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
int atracID = AllocAndRegisterAtrac(track.codecType);
|
int atracID = AllocAndRegisterAtrac(track.codecType);
|
||||||
|
@ -952,6 +963,32 @@ static int sceAtracSetAA3DataAndGetID(u32 buffer, u32 bufferSize, u32 fileSize,
|
||||||
return hleDelayResult(hleLogDebug(Log::ME, atracID), "atrac set aa3 data", 100);
|
return hleDelayResult(hleLogDebug(Log::ME, atracID), "atrac set aa3 data", 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sceAtracSetAA3HalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bufferSize, u32 fileSize) {
|
||||||
|
if (readSize > bufferSize) {
|
||||||
|
return hleLogError(Log::ME, SCE_ERROR_ATRAC_INCORRECT_READ_SIZE, "read size too large");
|
||||||
|
}
|
||||||
|
|
||||||
|
Track track;
|
||||||
|
std::string error;
|
||||||
|
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error);
|
||||||
|
if (ret < 0) {
|
||||||
|
return hleLogError(Log::ME, ret, "%s", error.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
int atracID = AllocAndRegisterAtrac(track.codecType);
|
||||||
|
if (atracID < 0) {
|
||||||
|
return hleLogError(Log::ME, atracID, "no free ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = atracContexts[atracID]->SetData(track, buffer, readSize, bufferSize, 2);
|
||||||
|
if (ret < 0) {
|
||||||
|
UnregisterAndDeleteAtrac(atracID);
|
||||||
|
return hleLogError(Log::ME, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hleDelayResult(hleLogDebug(Log::ME, atracID), "atrac set data", 100);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Should see if these are stored contiguously in memory somewhere, or if there really are
|
// TODO: Should see if these are stored contiguously in memory somewhere, or if there really are
|
||||||
// individual allocations being used.
|
// individual allocations being used.
|
||||||
static u32 _sceAtracGetContextAddress(int atracID) {
|
static u32 _sceAtracGetContextAddress(int atracID) {
|
||||||
|
@ -1048,31 +1085,6 @@ static int sceAtracLowLevelDecode(int atracID, u32 sourceAddr, u32 sourceBytesCo
|
||||||
return hleDelayResult(hleLogDebug(Log::ME, retval), "low level atrac decode data", atracDecodeDelay);
|
return hleDelayResult(hleLogDebug(Log::ME, retval), "low level atrac decode data", atracDecodeDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sceAtracSetAA3HalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bufferSize, u32 fileSize) {
|
|
||||||
if (readSize > bufferSize) {
|
|
||||||
return hleLogError(Log::ME, SCE_ERROR_ATRAC_INCORRECT_READ_SIZE, "read size too large");
|
|
||||||
}
|
|
||||||
|
|
||||||
Track track;
|
|
||||||
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
|
|
||||||
if (ret < 0) {
|
|
||||||
return hleLogError(Log::ME, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
int atracID = AllocAndRegisterAtrac(track.codecType);
|
|
||||||
if (atracID < 0) {
|
|
||||||
return hleLogError(Log::ME, atracID, "no free ID");
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = atracContexts[atracID]->SetData(track, buffer, readSize, bufferSize, 2);
|
|
||||||
if (ret < 0) {
|
|
||||||
UnregisterAndDeleteAtrac(atracID);
|
|
||||||
return hleLogError(Log::ME, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hleDelayResult(hleLogDebug(Log::ME, atracID), "atrac set data", 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// These three are the external interface used by sceSas' AT3 integration.
|
// These three are the external interface used by sceSas' AT3 integration.
|
||||||
|
|
||||||
// NOTE: There are special rules.
|
// NOTE: There are special rules.
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "sceAudiocodec.h"
|
#include "Core/HLE/sceAudiocodec.h"
|
||||||
|
#include "Core/Util/AtracTrack.h"
|
||||||
|
|
||||||
class PointerWrap;
|
class PointerWrap;
|
||||||
|
|
||||||
|
@ -29,9 +30,6 @@ void __AtracShutdown();
|
||||||
void __AtracNotifyLoadModule(int version, u32 crc, u32 bssAddr, int bssSize);
|
void __AtracNotifyLoadModule(int version, u32 crc, u32 bssAddr, int bssSize);
|
||||||
void __AtracNotifyUnloadModule();
|
void __AtracNotifyUnloadModule();
|
||||||
|
|
||||||
constexpr u32 ATRAC3_MAX_SAMPLES = 0x400; // 1024
|
|
||||||
constexpr u32 ATRAC3PLUS_MAX_SAMPLES = 0x800; // 2048
|
|
||||||
|
|
||||||
// The "state" member of SceAtracIdInfo.
|
// The "state" member of SceAtracIdInfo.
|
||||||
enum AtracStatus : u8 {
|
enum AtracStatus : u8 {
|
||||||
ATRAC_STATUS_UNINITIALIZED = 0, // bad state
|
ATRAC_STATUS_UNINITIALIZED = 0, // bad state
|
||||||
|
@ -68,6 +66,9 @@ const char *AtracStatusToString(AtracStatus status);
|
||||||
inline bool AtracStatusIsStreaming(AtracStatus status) {
|
inline bool AtracStatusIsStreaming(AtracStatus status) {
|
||||||
return (status & ATRAC_STATUS_STREAMED_MASK) != 0;
|
return (status & ATRAC_STATUS_STREAMED_MASK) != 0;
|
||||||
}
|
}
|
||||||
|
inline bool AtracStatusIsNormal(AtracStatus status) {
|
||||||
|
return (int)status >= ATRAC_STATUS_ALL_DATA_LOADED && (int)status <= ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER;
|
||||||
|
}
|
||||||
|
|
||||||
struct SceAtracIdInfo {
|
struct SceAtracIdInfo {
|
||||||
s32 decodePos; // Sample position in the song that we'll next be decoding from.
|
s32 decodePos; // Sample position in the song that we'll next be decoding from.
|
||||||
|
|
300
Core/Util/AtracTrack.cpp
Normal file
300
Core/Util/AtracTrack.cpp
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
#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;
|
||||||
|
}
|
114
Core/Util/AtracTrack.h
Normal file
114
Core/Util/AtracTrack.h
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Atrac file parsing.
|
||||||
|
#define AT3_MAGIC 0x0270
|
||||||
|
#define AT3_PLUS_MAGIC 0xFFFE
|
||||||
|
#define PSP_MODE_AT_3_PLUS 0x00001000
|
||||||
|
#define PSP_MODE_AT_3 0x00001001
|
||||||
|
|
||||||
|
constexpr u32 ATRAC3_MAX_SAMPLES = 0x400; // 1024
|
||||||
|
constexpr u32 ATRAC3PLUS_MAX_SAMPLES = 0x800; // 2048
|
||||||
|
|
||||||
|
struct AtracLoopInfo {
|
||||||
|
int cuePointID;
|
||||||
|
int type;
|
||||||
|
int startSample;
|
||||||
|
int endSample;
|
||||||
|
int fraction;
|
||||||
|
int playCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is (mostly) constant info, once a track has been loaded.
|
||||||
|
struct Track {
|
||||||
|
// This both does and doesn't belong in Track - it's fixed for an Atrac instance. Oh well.
|
||||||
|
u32 codecType = 0;
|
||||||
|
|
||||||
|
// Size of the full track being streamed or played. Can be a lot larger than the in-memory buffer in the streaming modes.
|
||||||
|
u32 fileSize = 0;
|
||||||
|
|
||||||
|
// Not really used for much except queries, this keeps track of the bitrate of the track (kbps).
|
||||||
|
u32 bitrate = 64;
|
||||||
|
|
||||||
|
// Signifies whether to use a more efficient coding mode with less stereo separation. For our purposes, just metadata,
|
||||||
|
// not actually used in decoding.
|
||||||
|
int jointStereo = 0;
|
||||||
|
|
||||||
|
// Number of audio channels in the track.
|
||||||
|
u16 channels = 2;
|
||||||
|
|
||||||
|
// The size of an encoded frame in bytes.
|
||||||
|
u16 bytesPerFrame = 0;
|
||||||
|
|
||||||
|
// Byte offset of the first encoded frame in the input buffer. Note: Some samples may be skipped according to firstSampleOffset.
|
||||||
|
int dataByteOffset = 0;
|
||||||
|
|
||||||
|
// How many samples to skip from the beginning of a track when decoding.
|
||||||
|
// Actually, the real number is this added to FirstOffsetExtra(codecType). You can call
|
||||||
|
// FirstSampleOffset2() to get that.
|
||||||
|
// Some use of these offsets around the code seem to be inconsistent, sometimes the extra is included,
|
||||||
|
// sometimes not.
|
||||||
|
int firstSampleOffset = 0;
|
||||||
|
|
||||||
|
// Last sample number. Inclusive. Though, we made it so that in Analyze, it's exclusive in the file.
|
||||||
|
// Does not take firstSampleOffset into account.
|
||||||
|
int endSample = -1;
|
||||||
|
|
||||||
|
// NOTE: The below CAN be written.
|
||||||
|
// Loop configuration. The PSP only supports one loop but we store them all.
|
||||||
|
std::vector<AtracLoopInfo> loopinfo;
|
||||||
|
// The actual used loop offsets. These appear to be raw offsets, not taking FirstSampleOffset2() into account.
|
||||||
|
int loopStartSample = -1;
|
||||||
|
int loopEndSample = -1;
|
||||||
|
|
||||||
|
// Input frame size
|
||||||
|
int BytesPerFrame() const {
|
||||||
|
return bytesPerFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int FirstOffsetExtra() const {
|
||||||
|
// These first samples are skipped, after first possibly skipping 0-2 full frames, it seems.
|
||||||
|
return codecType == PSP_MODE_AT_3_PLUS ? 0x170 : 0x45;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Includes the extra offset. See firstSampleOffset comment above.
|
||||||
|
int FirstSampleOffsetFull() const {
|
||||||
|
return FirstOffsetExtra() + firstSampleOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output frame size, different between the two supported codecs.
|
||||||
|
int SamplesPerFrame() const {
|
||||||
|
return codecType == PSP_MODE_AT_3_PLUS ? ATRAC3PLUS_MAX_SAMPLES : ATRAC3_MAX_SAMPLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Bitrate() const {
|
||||||
|
int bitrate = (bytesPerFrame * 352800) / 1000;
|
||||||
|
if (codecType == PSP_MODE_AT_3_PLUS)
|
||||||
|
bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0;
|
||||||
|
else
|
||||||
|
bitrate = (bitrate + 511) >> 10;
|
||||||
|
return bitrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This appears to be buggy, should probably include FirstOffsetExtra?
|
||||||
|
// Actually the units don't even make sense here.
|
||||||
|
int DecodePosBySample(int sample) const {
|
||||||
|
return (u32)(firstSampleOffset + sample / (int)SamplesPerFrame() * bytesPerFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This appears to be buggy, should probably include FirstOffsetExtra?
|
||||||
|
int FileOffsetBySample(int sample) const {
|
||||||
|
int offsetSample = sample + firstSampleOffset;
|
||||||
|
int frameOffset = offsetSample / (int)SamplesPerFrame();
|
||||||
|
return (u32)(dataByteOffset + bytesPerFrame + frameOffset * bytesPerFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugLog() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
|
@ -326,6 +326,9 @@ bool MainUI::HandleCustomEvent(QEvent *e) {
|
||||||
case BrowseFileType::ZIP:
|
case BrowseFileType::ZIP:
|
||||||
filter = "ZIP files (*.zip)";
|
filter = "ZIP files (*.zip)";
|
||||||
break;
|
break;
|
||||||
|
case BrowseFileType::ATRAC3:
|
||||||
|
filter = "AT3 files (*.at3)";
|
||||||
|
break;
|
||||||
case BrowseFileType::ANY:
|
case BrowseFileType::ANY:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,9 @@ void DarwinFileSystemServices::presentDirectoryPanel(
|
||||||
case BrowseFileType::SOUND_EFFECT:
|
case BrowseFileType::SOUND_EFFECT:
|
||||||
[panel setAllowedFileTypes:[NSArray arrayWithObject:@"wav"]];
|
[panel setAllowedFileTypes:[NSArray arrayWithObject:@"wav"]];
|
||||||
break;
|
break;
|
||||||
|
case BrowseFileType::ATRAC3:
|
||||||
|
[panel setAllowedFileTypes:[NSArray arrayWithObject:@"at3"]];
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include "Core/HLE/sceNetAdhocMatching.h"
|
#include "Core/HLE/sceNetAdhocMatching.h"
|
||||||
#include "Common/System/Request.h"
|
#include "Common/System/Request.h"
|
||||||
|
|
||||||
|
#include "Core/Util/AtracTrack.h"
|
||||||
#include "Core/HLE/sceAtrac.h"
|
#include "Core/HLE/sceAtrac.h"
|
||||||
#include "Core/HLE/sceAudio.h"
|
#include "Core/HLE/sceAudio.h"
|
||||||
#include "Core/HLE/sceAudiocodec.h"
|
#include "Core/HLE/sceAudiocodec.h"
|
||||||
|
@ -1000,7 +1001,7 @@ void DrawAudioDecodersView(ImConfig &cfg, ImControl &control) {
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text("in:%d out:%d", ctx->Channels(), ctx->GetOutputChannels());
|
ImGui::Text("in:%d out:%d", ctx->Channels(), ctx->GetOutputChannels());
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
if (ctx->BufferState() != ATRAC_STATUS_LOW_LEVEL) {
|
if (AtracStatusIsNormal(ctx->BufferState())) {
|
||||||
int pos;
|
int pos;
|
||||||
ctx->GetNextDecodePosition(&pos);
|
ctx->GetNextDecodePosition(&pos);
|
||||||
ImGui::Text("%d", pos);
|
ImGui::Text("%d", pos);
|
||||||
|
@ -1008,7 +1009,7 @@ void DrawAudioDecodersView(ImConfig &cfg, ImControl &control) {
|
||||||
ImGui::TextUnformatted("N/A");
|
ImGui::TextUnformatted("N/A");
|
||||||
}
|
}
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
if (ctx->BufferState() <= ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER) {
|
if (AtracStatusIsNormal(ctx->BufferState())) {
|
||||||
ImGui::Text("%d", ctx->RemainingFrames());
|
ImGui::Text("%d", ctx->RemainingFrames());
|
||||||
} else {
|
} else {
|
||||||
ImGui::TextUnformatted("N/A");
|
ImGui::TextUnformatted("N/A");
|
||||||
|
@ -1479,6 +1480,44 @@ static void DrawModules(const MIPSDebugInterface *debug, ImConfig &cfg, ImContro
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImAtracToolWindow::Draw(ImConfig &cfg) {
|
||||||
|
if (!ImGui::Begin("Atrac Tool", &cfg.atracToolOpen) || !g_symbolMap) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::InputText("File", atracPath_, sizeof(atracPath_));
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Choose...")) {
|
||||||
|
System_BrowseForFile(cfg.requesterToken, "Choose AT3 file", BrowseFileType::ATRAC3, [&](const std::string &filename, int) {
|
||||||
|
truncate_cpy(atracPath_, filename);
|
||||||
|
}, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(atracPath_) > 0) {
|
||||||
|
if (ImGui::Button("Load")) {
|
||||||
|
track_.reset(new Track());
|
||||||
|
std::string data;
|
||||||
|
if (File::ReadBinaryFileToString(Path(atracPath_), &data)) {
|
||||||
|
AnalyzeAtracTrack((const u8 *)data.data(), (u32)data.size(), track_.get(), &error_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track_.get() != 0) {
|
||||||
|
ImGui::Text("Codec: %s", track_->codecType != PSP_CODEC_AT3 ? "at3+" : "at3");
|
||||||
|
ImGui::Text("Bitrate: %d kbps Channels: %d", track_->Bitrate(), track_->channels);
|
||||||
|
ImGui::Text("Frame size in bytes: %d Output frame in samples: %d", track_->BytesPerFrame(), track_->SamplesPerFrame());
|
||||||
|
ImGui::Text("First valid sample: %08x", track_->FirstSampleOffsetFull());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error_.empty()) {
|
||||||
|
ImGui::TextUnformatted(error_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
void DrawHLEModules(ImConfig &config) {
|
void DrawHLEModules(ImConfig &config) {
|
||||||
if (!ImGui::Begin("HLE Modules", &config.hleModulesOpen)) {
|
if (!ImGui::Begin("HLE Modules", &config.hleModulesOpen)) {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
@ -1679,6 +1718,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
|
||||||
ImGui::MenuItem("Debug stats", nullptr, &cfg_.debugStatsOpen);
|
ImGui::MenuItem("Debug stats", nullptr, &cfg_.debugStatsOpen);
|
||||||
ImGui::MenuItem("Struct viewer", nullptr, &cfg_.structViewerOpen);
|
ImGui::MenuItem("Struct viewer", nullptr, &cfg_.structViewerOpen);
|
||||||
ImGui::MenuItem("Log channels", nullptr, &cfg_.logConfigOpen);
|
ImGui::MenuItem("Log channels", nullptr, &cfg_.logConfigOpen);
|
||||||
|
ImGui::MenuItem("Atrac Tool", nullptr, &cfg_.atracToolOpen);
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
if (ImGui::BeginMenu("Misc")) {
|
if (ImGui::BeginMenu("Misc")) {
|
||||||
|
@ -1761,6 +1801,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
|
||||||
DrawHLEModules(cfg_);
|
DrawHLEModules(cfg_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cfg_.atracToolOpen) {
|
||||||
|
atracToolWindow_.Draw(cfg_);
|
||||||
|
}
|
||||||
|
|
||||||
if (cfg_.framebuffersOpen) {
|
if (cfg_.framebuffersOpen) {
|
||||||
DrawFramebuffersWindow(cfg_, gpuDebug->GetFramebufferManagerCommon());
|
DrawFramebuffersWindow(cfg_, gpuDebug->GetFramebufferManagerCommon());
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,7 @@ struct ImConfig {
|
||||||
bool sasAudioOpen;
|
bool sasAudioOpen;
|
||||||
bool logConfigOpen;
|
bool logConfigOpen;
|
||||||
bool utilityModulesOpen;
|
bool utilityModulesOpen;
|
||||||
|
bool atracToolOpen;
|
||||||
bool memViewOpen[4];
|
bool memViewOpen[4];
|
||||||
|
|
||||||
// HLE explorer settings
|
// HLE explorer settings
|
||||||
|
@ -176,6 +177,7 @@ struct ImConfig {
|
||||||
|
|
||||||
bool sasShowAllVoices = false;
|
bool sasShowAllVoices = false;
|
||||||
|
|
||||||
|
|
||||||
// We use a separate ini file from the main PPSSPP config.
|
// We use a separate ini file from the main PPSSPP config.
|
||||||
void LoadConfig(const Path &iniFile);
|
void LoadConfig(const Path &iniFile);
|
||||||
void SaveConfig(const Path &iniFile);
|
void SaveConfig(const Path &iniFile);
|
||||||
|
@ -183,6 +185,16 @@ struct ImConfig {
|
||||||
void SyncConfig(IniFile *ini, bool save);
|
void SyncConfig(IniFile *ini, bool save);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Track;
|
||||||
|
class ImAtracToolWindow {
|
||||||
|
public:
|
||||||
|
void Draw(ImConfig &cfg);
|
||||||
|
|
||||||
|
char atracPath_[1024]{};
|
||||||
|
std::unique_ptr<Track> track_;
|
||||||
|
std::string error_;
|
||||||
|
};
|
||||||
|
|
||||||
enum class ImCmd {
|
enum class ImCmd {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
TRIGGER_FIND_POPUP,
|
TRIGGER_FIND_POPUP,
|
||||||
|
@ -233,6 +245,7 @@ private:
|
||||||
ImStructViewer structViewer_;
|
ImStructViewer structViewer_;
|
||||||
ImGePixelViewerWindow pixelViewer_;
|
ImGePixelViewerWindow pixelViewer_;
|
||||||
ImMemDumpWindow memDumpWindow_;
|
ImMemDumpWindow memDumpWindow_;
|
||||||
|
ImAtracToolWindow atracToolWindow_;
|
||||||
|
|
||||||
ImSnapshotState newSnapshot_;
|
ImSnapshotState newSnapshot_;
|
||||||
ImSnapshotState snapshot_;
|
ImSnapshotState snapshot_;
|
||||||
|
|
|
@ -333,6 +333,7 @@
|
||||||
<ClInclude Include="..\..\Core\ThreadEventQueue.h" />
|
<ClInclude Include="..\..\Core\ThreadEventQueue.h" />
|
||||||
<ClInclude Include="..\..\Core\ThreadPools.h" />
|
<ClInclude Include="..\..\Core\ThreadPools.h" />
|
||||||
<ClInclude Include="..\..\Core\TiltEventProcessor.h" />
|
<ClInclude Include="..\..\Core\TiltEventProcessor.h" />
|
||||||
|
<ClInclude Include="..\..\Core\Util\AtracTrack.h" />
|
||||||
<ClInclude Include="..\..\Core\Util\GameDB.h" />
|
<ClInclude Include="..\..\Core\Util\GameDB.h" />
|
||||||
<ClInclude Include="..\..\Core\Util\MemStick.h" />
|
<ClInclude Include="..\..\Core\Util\MemStick.h" />
|
||||||
<ClInclude Include="..\..\Core\Util\PortManager.h" />
|
<ClInclude Include="..\..\Core\Util\PortManager.h" />
|
||||||
|
@ -632,6 +633,7 @@
|
||||||
<ClCompile Include="..\..\Core\System.cpp" />
|
<ClCompile Include="..\..\Core\System.cpp" />
|
||||||
<ClCompile Include="..\..\Core\ThreadPools.cpp" />
|
<ClCompile Include="..\..\Core\ThreadPools.cpp" />
|
||||||
<ClCompile Include="..\..\Core\TiltEventProcessor.cpp" />
|
<ClCompile Include="..\..\Core\TiltEventProcessor.cpp" />
|
||||||
|
<ClCompile Include="..\..\Core\Util\AtracTrack.cpp" />
|
||||||
<ClCompile Include="..\..\Core\Util\GameDB.cpp" />
|
<ClCompile Include="..\..\Core\Util\GameDB.cpp" />
|
||||||
<ClCompile Include="..\..\Core\Util\MemStick.cpp" />
|
<ClCompile Include="..\..\Core\Util\MemStick.cpp" />
|
||||||
<ClCompile Include="..\..\Core\Util\PortManager.cpp" />
|
<ClCompile Include="..\..\Core\Util\PortManager.cpp" />
|
||||||
|
@ -1040,4 +1042,4 @@
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -1235,6 +1235,9 @@
|
||||||
<ClCompile Include="..\..\Core\HLE\SocketManager.cpp">
|
<ClCompile Include="..\..\Core\HLE\SocketManager.cpp">
|
||||||
<Filter>HLE</Filter>
|
<Filter>HLE</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\Core\Util\AtracTrack.cpp">
|
||||||
|
<Filter>Util</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
|
@ -1954,10 +1957,13 @@
|
||||||
<ClInclude Include="..\..\Core\HLE\SocketManager.h">
|
<ClInclude Include="..\..\Core\HLE\SocketManager.h">
|
||||||
<Filter>HLE</Filter>
|
<Filter>HLE</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\Core\Util\AtracTrack.h">
|
||||||
|
<Filter>Util</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\..\ext\gason\LICENSE">
|
<None Include="..\..\ext\gason\LICENSE">
|
||||||
<Filter>Ext\gason</Filter>
|
<Filter>Ext\gason</Filter>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -517,7 +517,7 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
|
||||||
supportedExtensions = { ".zip" };
|
supportedExtensions = { ".zip" };
|
||||||
break;
|
break;
|
||||||
case BrowseFileType::SYMBOL_MAP:
|
case BrowseFileType::SYMBOL_MAP:
|
||||||
supportedExtensions = { ".map" };
|
supportedExtensions = { ".ppmap" };
|
||||||
break;
|
break;
|
||||||
case BrowseFileType::DB:
|
case BrowseFileType::DB:
|
||||||
supportedExtensions = { ".db" };
|
supportedExtensions = { ".db" };
|
||||||
|
@ -525,6 +525,9 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
|
||||||
case BrowseFileType::SOUND_EFFECT:
|
case BrowseFileType::SOUND_EFFECT:
|
||||||
supportedExtensions = { ".wav", ".mp3" };
|
supportedExtensions = { ".wav", ".mp3" };
|
||||||
break;
|
break;
|
||||||
|
case BrowseFileType::ATRAC3:
|
||||||
|
supportedExtensions = { ".at3" };
|
||||||
|
break;
|
||||||
case BrowseFileType::ANY:
|
case BrowseFileType::ANY:
|
||||||
// 'ChooseFile' will added '*' by default when there are no extensions assigned
|
// 'ChooseFile' will added '*' by default when there are no extensions assigned
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -537,6 +537,8 @@ static std::wstring MakeWindowsFilter(BrowseFileType type) {
|
||||||
return FinalizeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||");
|
return FinalizeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||");
|
||||||
case BrowseFileType::SYMBOL_MAP:
|
case BrowseFileType::SYMBOL_MAP:
|
||||||
return FinalizeFilter(L"Symbol map files (*.ppmap)|*.ppmap|All files (*.*)|*.*||");
|
return FinalizeFilter(L"Symbol map files (*.ppmap)|*.ppmap|All files (*.*)|*.*||");
|
||||||
|
case BrowseFileType::ATRAC3:
|
||||||
|
return FinalizeFilter(L"ATRAC3/3+ files (*.at3)|*.at3|All files (*.*)|*.*||");
|
||||||
case BrowseFileType::ANY:
|
case BrowseFileType::ANY:
|
||||||
return FinalizeFilter(L"All files (*.*)|*.*||");
|
return FinalizeFilter(L"All files (*.*)|*.*||");
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -753,6 +753,7 @@ EXEC_AND_LIB_FILES := \
|
||||||
$(SRC)/Core/MIPS/JitCommon/JitCommon.cpp \
|
$(SRC)/Core/MIPS/JitCommon/JitCommon.cpp \
|
||||||
$(SRC)/Core/MIPS/JitCommon/JitBlockCache.cpp \
|
$(SRC)/Core/MIPS/JitCommon/JitBlockCache.cpp \
|
||||||
$(SRC)/Core/MIPS/JitCommon/JitState.cpp \
|
$(SRC)/Core/MIPS/JitCommon/JitState.cpp \
|
||||||
|
$(SRC)/Core/Util/AtracTrack.cpp \
|
||||||
$(SRC)/Core/Util/AudioFormat.cpp \
|
$(SRC)/Core/Util/AudioFormat.cpp \
|
||||||
$(SRC)/Core/Util/MemStick.cpp \
|
$(SRC)/Core/Util/MemStick.cpp \
|
||||||
$(SRC)/Core/Util/PortManager.cpp \
|
$(SRC)/Core/Util/PortManager.cpp \
|
||||||
|
|
|
@ -690,8 +690,8 @@ SOURCES_CXX += \
|
||||||
$(COREDIR)/Font/PGF.cpp \
|
$(COREDIR)/Font/PGF.cpp \
|
||||||
$(COREDIR)/HLE/HLE.cpp \
|
$(COREDIR)/HLE/HLE.cpp \
|
||||||
$(COREDIR)/HLE/KUBridge.cpp \
|
$(COREDIR)/HLE/KUBridge.cpp \
|
||||||
$(COREDIR)/HLE/NetInetConstants.cpp \
|
$(COREDIR)/HLE/NetInetConstants.cpp \
|
||||||
$(COREDIR)/HLE/SocketManager.cpp \
|
$(COREDIR)/HLE/SocketManager.cpp \
|
||||||
$(COREDIR)/HLE/Plugins.cpp \
|
$(COREDIR)/HLE/Plugins.cpp \
|
||||||
$(COREDIR)/HLE/sceSha256.cpp \
|
$(COREDIR)/HLE/sceSha256.cpp \
|
||||||
$(COREDIR)/HLE/sceSircs.cpp \
|
$(COREDIR)/HLE/sceSircs.cpp \
|
||||||
|
@ -826,6 +826,7 @@ SOURCES_CXX += \
|
||||||
$(COREDIR)/Screenshot.cpp \
|
$(COREDIR)/Screenshot.cpp \
|
||||||
$(COREDIR)/System.cpp \
|
$(COREDIR)/System.cpp \
|
||||||
$(COREDIR)/ThreadPools.cpp \
|
$(COREDIR)/ThreadPools.cpp \
|
||||||
|
$(COREDIR)/Util/AtracTrack.cpp \
|
||||||
$(COREDIR)/Util/BlockAllocator.cpp \
|
$(COREDIR)/Util/BlockAllocator.cpp \
|
||||||
$(COREDIR)/Util/MemStick.cpp \
|
$(COREDIR)/Util/MemStick.cpp \
|
||||||
$(COREDIR)/Util/PPGeDraw.cpp \
|
$(COREDIR)/Util/PPGeDraw.cpp \
|
||||||
|
|
Loading…
Add table
Reference in a new issue