From fde59c955d13dedc08fde09109d12a13d8b6b793 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 28 Apr 2019 11:56:39 -0700 Subject: [PATCH 1/6] Mp3: Use a vector for the temp buffer. Hopefully will move to reading RAM directly. I think this was not always adding data properly, as I got wrong output after decode. Makes more sense as a vector, anyway. --- Core/HW/SimpleAudioDec.cpp | 6 ++++-- Core/HW/SimpleAudioDec.h | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Core/HW/SimpleAudioDec.cpp b/Core/HW/SimpleAudioDec.cpp index ed897dcbc9..472011f224 100644 --- a/Core/HW/SimpleAudioDec.cpp +++ b/Core/HW/SimpleAudioDec.cpp @@ -351,7 +351,8 @@ u32 AuCtx::AuDecode(u32 pcmAddr) { // get consumed source length int srcPos = decoder->GetSourcePos() + nextSync; // remove the consumed source - sourcebuff.erase(0, srcPos); + if (srcPos > 0) + sourcebuff.erase(sourcebuff.begin(), sourcebuff.begin() + srcPos); // reduce the available Aubuff size // (the available buff size is now used to know if we can read again from file and how many to read) AuBufAvailable -= srcPos; @@ -430,7 +431,8 @@ u32 AuCtx::AuNotifyAddStreamData(int size) { } if (Memory::IsValidRange(AuBuf, size)) { - sourcebuff.append((const char *)Memory::GetPointer(AuBuf + offset), size); + sourcebuff.resize(sourcebuff.size() + size); + Memory::MemcpyUnchecked(&sourcebuff[sourcebuff.size() - size], AuBuf + offset, size); } return 0; diff --git a/Core/HW/SimpleAudioDec.h b/Core/HW/SimpleAudioDec.h index 1adad1c2cf..1dc8aa28e5 100644 --- a/Core/HW/SimpleAudioDec.h +++ b/Core/HW/SimpleAudioDec.h @@ -119,7 +119,11 @@ public: void DoState(PointerWrap &p); void EatSourceBuff(int amount) { - sourcebuff.erase(0, amount); + if (amount > sourcebuff.size()) { + amount = (int)sourcebuff.size(); + } + if (amount > 0) + sourcebuff.erase(sourcebuff.begin(), sourcebuff.begin() + amount); AuBufAvailable -= amount; } // Au source information. Written to from for example sceAacInit so public for now. @@ -155,7 +159,7 @@ public: private: size_t FindNextMp3Sync(); - std::string sourcebuff; // source buffer + std::vector sourcebuff; // source buffer }; From 4b4c0f9bda1af38297253aeb2aeab28a5cc7ca0e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 28 Apr 2019 11:57:59 -0700 Subject: [PATCH 2/6] Mp3: Always output data in decode, except at end. Turns out this doesn't return 0 until the end, even if there's no data available to decode. It just writes zeros in that case. --- Core/HW/SimpleAudioDec.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Core/HW/SimpleAudioDec.cpp b/Core/HW/SimpleAudioDec.cpp index 472011f224..29124871da 100644 --- a/Core/HW/SimpleAudioDec.cpp +++ b/Core/HW/SimpleAudioDec.cpp @@ -332,7 +332,6 @@ u32 AuCtx::AuDecode(u32 pcmAddr) { } auto outbuf = Memory::GetPointer(PCMBuf); - memset(outbuf, 0, PCMBufSize); // important! empty outbuf to avoid noise int outpcmbufsize = 0; // Decode a single frame in sourcebuff and output into PCMBuf. @@ -342,7 +341,7 @@ u32 AuCtx::AuDecode(u32 pcmAddr) { decoder->Decode(&sourcebuff[nextSync], (int)sourcebuff.size() - nextSync, outbuf, &outpcmbufsize); if (outpcmbufsize == 0) { - // no output pcm, we are at the end of the stream + // Nothing was output, hopefully we're at the end of the stream. AuBufAvailable = 0; sourcebuff.clear(); } else { @@ -359,7 +358,8 @@ u32 AuCtx::AuDecode(u32 pcmAddr) { } } - if (sourcebuff.empty() && LoopNum != 0) { + bool end = readPos - AuBufAvailable >= (int64_t)endPos; + if (end && LoopNum != 0) { // When looping, start the sum back off at zero and reset readPos to the start. SumDecodedSamples = 0; readPos = startPos; @@ -367,6 +367,14 @@ u32 AuCtx::AuDecode(u32 pcmAddr) { LoopNum--; } + if (outpcmbufsize == 0 && !end) { + outpcmbufsize = MaxOutputSample * 4; + memset(outbuf, 0, PCMBufSize); + } else if ((u32)outpcmbufsize < PCMBufSize) { + // TODO: We probably should use a rolling buffer instead. + memset(outbuf + outpcmbufsize, 0, PCMBufSize - outpcmbufsize); + } + Memory::Write_U32(PCMBuf, pcmAddr); return outpcmbufsize; } From bfa9aa009da3bf98093be5f84501f3aff3e8e2a4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 28 Apr 2019 11:59:07 -0700 Subject: [PATCH 3/6] Mp3: Properly flush the buffer on reset. --- Core/HLE/sceMp3.cpp | 11 ++++++----- Core/HW/SimpleAudioDec.cpp | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Core/HLE/sceMp3.cpp b/Core/HLE/sceMp3.cpp index 07a5e78cf0..56d5c0838a 100644 --- a/Core/HLE/sceMp3.cpp +++ b/Core/HLE/sceMp3.cpp @@ -171,15 +171,16 @@ static int sceMp3Decode(u32 mp3, u32 outPcmPtr) { } static int sceMp3ResetPlayPosition(u32 mp3) { - DEBUG_LOG(ME, "SceMp3ResetPlayPosition(%08x)", mp3); - AuCtx *ctx = getMp3Ctx(mp3); if (!ctx) { - ERROR_LOG(ME, "%s: bad mp3 handle %08x", __FUNCTION__, mp3); - return -1; + if (mp3 >= MP3_MAX_HANDLES) + return hleLogError(ME, ERROR_MP3_INVALID_HANDLE, "invalid handle"); + return hleLogError(ME, ERROR_MP3_NOT_YET_INIT_HANDLE, "unreserved handle"); + } else if (ctx->Version < 0 || ctx->AuBuf == 0) { + return hleLogError(ME, ERROR_MP3_NOT_YET_INIT_HANDLE, "not yet init"); } - return ctx->AuResetPlayPosition(); + return hleLogSuccessI(ME, ctx->AuResetPlayPosition()); } static int sceMp3CheckStreamDataNeeded(u32 mp3) { diff --git a/Core/HW/SimpleAudioDec.cpp b/Core/HW/SimpleAudioDec.cpp index 29124871da..ae1715a6ae 100644 --- a/Core/HW/SimpleAudioDec.cpp +++ b/Core/HW/SimpleAudioDec.cpp @@ -481,6 +481,9 @@ u32 AuCtx::AuResetPlayPositionByFrame(int position) { u32 AuCtx::AuResetPlayPosition() { readPos = startPos; + SumDecodedSamples = 0; + AuBufAvailable = 0; + sourcebuff.clear(); return 0; } From 68d257168127fc7b66df033da7d82d85c8ddd9fa Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 28 Apr 2019 12:10:20 -0700 Subject: [PATCH 4/6] Mp3: Fix ID3 tag handling. Just ignore them and handle via frame sync, which is what the PSP does.. --- Core/HLE/sceMp3.cpp | 55 ++++++++++++++------------------------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/Core/HLE/sceMp3.cpp b/Core/HLE/sceMp3.cpp index 56d5c0838a..b8c219ad49 100644 --- a/Core/HLE/sceMp3.cpp +++ b/Core/HLE/sceMp3.cpp @@ -357,20 +357,20 @@ static int CalculateMp3SamplesPerFrame(int versionBits, int layerBits) { } } -static int ParseMp3Header(AuCtx *ctx, int offset, bool *isID3) { - u32 ptr = ctx->AuBuf + ctx->AuStreamWorkareaSize() + offset; - int header = bswap32(Memory::Read_U32(ptr)); - // ID3 tag , can be seen in Hanayaka Nari Wa ga Ichizoku. - static const int ID3 = 0x49443300; - if ((header & 0xFFFFFF00) == ID3) { - *isID3 = true; - // TODO: Can we count on startPos being read already? What if it's past the buffer size? - int size = bswap32(Memory::Read_U32(ptr + ctx->startPos + 6)); - // Highest bit of each byte has to be ignored (format: 0x7F7F7F7F) - size = (size & 0x7F) | ((size & 0x7F00) >> 1) | ((size & 0x7F0000) >> 2) | ((size & 0x7F000000) >> 3); - header = bswap32(Memory::Read_U32(ptr + ctx->startPos + 10 + size)); +static int FindMp3Header(AuCtx *ctx, int &header, int end) { + u32 addr = ctx->AuBuf + ctx->AuStreamWorkareaSize(); + if (Memory::IsValidRange(addr, end)) { + u8 *ptr = Memory::GetPointerUnchecked(addr); + for (int offset = 0; offset < end; ++offset) { + // If we hit valid sync bits, then we've found a header. + if (ptr[offset] == 0xFF && (ptr[offset + 1] & 0xC0) == 0xC0) { + header = bswap32(Memory::Read_U32(addr + offset)); + return offset; + } + } } - return header; + + return -1; } static int sceMp3Init(u32 mp3) { @@ -383,25 +383,13 @@ static int sceMp3Init(u32 mp3) { return hleLogError(ME, ERROR_MP3_UNRESERVED_HANDLE, "incorrect handle type"); } - // First, let's search for the MP3 header. It can be offset by at most 1439 bytes. - bool hasID3Tag = false; - int header = 0; - for (int offset = 0; offset < 1440; ++offset) { - header = ParseMp3Header(ctx, offset, &hasID3Tag); - // If we hit valid sync bits, then we've found a header. - if ((header & 0xFFC00000) == 0xFFC00000) { - // Ignore the data before that. - ctx->EatSourceBuff(offset); - break; - } - } - static const int PARSE_DELAY_MS = 500; - // Couldn't find a header after all? - if ((header & 0xFFC00000) != 0xFFC00000) { + // First, let's search for the MP3 header. It can be offset by at most 1439 bytes. + // If we have an ID3 tag, we'll get past it based on frame sync. Don't modify startPos. + int header = 0; + if (FindMp3Header(ctx, header, 1440) < 0) return hleDelayResult(hleLogWarning(ME, ERROR_AVCODEC_INVALID_DATA, "no header found"), "mp3 init", PARSE_DELAY_MS); - } // Parse the Mp3 header int layer = (header >> 17) & 0x3; @@ -424,15 +412,6 @@ static int sceMp3Init(u32 mp3) { uint64_t totalBytes = (ctx->endPos & 0xFFFFFFFF) - (ctx->startPos & 0xFFFFFFFF); ctx->FrameNum = (int)((totalBytes * ctx->SamplingRate) / bytesPerSecond); - // For mp3 file, if ID3 tag is detected, we must move startPos to 0x400 (stream start position), remove 0x400 bytes of the sourcebuff, and reduce the available buffer size by 0x400 - // this is very important for ID3 tag mp3, since our universal audio decoder is for decoding stream part only. - if (hasID3Tag) { - // if get ID3 tage, we will decode from 0x400 - // TODO: This doesn't seem right. - ctx->startPos = 0x400; - ctx->EatSourceBuff(0x400); - } - DEBUG_LOG(ME, "sceMp3Init(): channels=%i, samplerate=%iHz, bitrate=%ikbps", ctx->Channels, ctx->SamplingRate, ctx->BitRate); if (layer != 1) { From 4ee5d2b611e125ae8b42572575c1b273286fb7a4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 28 Apr 2019 13:07:55 -0700 Subject: [PATCH 5/6] Mp3: Correct seek to specific frame. Before, it treated it as an (incomplete) seek to a byte position, which didn't make much sense. This matches PSP behavior per tests. --- Core/HLE/sceMp3.cpp | 21 ++++++++++++++------- Core/HW/SimpleAudioDec.cpp | 12 ++++++++++-- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Core/HLE/sceMp3.cpp b/Core/HLE/sceMp3.cpp index b8c219ad49..3a58cb5cb0 100644 --- a/Core/HLE/sceMp3.cpp +++ b/Core/HLE/sceMp3.cpp @@ -31,6 +31,7 @@ static const u32 ERROR_MP3_INVALID_HANDLE = 0x80671001; static const u32 ERROR_MP3_UNRESERVED_HANDLE = 0x80671102; static const u32 ERROR_MP3_NOT_YET_INIT_HANDLE = 0x80671103; static const u32 ERROR_MP3_NO_RESOURCE_AVAIL = 0x80671201; +static const u32 ERROR_MP3_BAD_RESET_FRAME = 0x80671501; static const u32 ERROR_MP3_BAD_ADDR = 0x80671002; static const u32 ERROR_MP3_BAD_SIZE = 0x80671003; static const u32 ERROR_AVCODEC_INVALID_DATA = 0x807f00fd; @@ -406,7 +407,7 @@ static int sceMp3Init(u32 mp3) { } // Based on bitrate, we can calculate the frame size in bytes. - // Note: this doesn't correctly handling padding or slot size, but the PSP doesn't either. + // Note: this doesn't correctly handle padding or slot size, but the PSP doesn't either. uint32_t bytesPerSecond = (ctx->MaxOutputSample / 8) * ctx->BitRate * 1000; // The frame count ignores the upper bits of these sizes, although they are used in cases. uint64_t totalBytes = (ctx->endPos & 0xFFFFFFFF) - (ctx->startPos & 0xFFFFFFFF); @@ -617,15 +618,21 @@ static u32 sceMp3GetMPEGVersion(u32 mp3) { return hleReportDebug(ME, ctx->AuGetVersion()); } -static u32 sceMp3ResetPlayPositionByFrame(u32 mp3, int position) { - DEBUG_LOG(ME, "sceMp3ResetPlayPositionByFrame(%08x, %i)", mp3, position); +static u32 sceMp3ResetPlayPositionByFrame(u32 mp3, u32 frame) { AuCtx *ctx = getMp3Ctx(mp3); if (!ctx) { - ERROR_LOG(ME, "%s: bad mp3 handle %08x", __FUNCTION__, mp3); - return -1; + if (mp3 >= MP3_MAX_HANDLES) + return hleLogError(ME, ERROR_MP3_INVALID_HANDLE, "invalid handle"); + return hleLogError(ME, ERROR_MP3_NOT_YET_INIT_HANDLE, "unreserved handle"); + } else if (ctx->Version < 0 || ctx->AuBuf == 0) { + return hleLogError(ME, ERROR_MP3_NOT_YET_INIT_HANDLE, "not yet init"); } - return ctx->AuResetPlayPositionByFrame(position); + if (frame >= ctx->AuGetFrameNum()) { + return hleLogError(ME, ERROR_MP3_BAD_RESET_FRAME, "bad frame position"); + } + + return hleLogSuccessI(ME, ctx->AuResetPlayPositionByFrame(frame)); } static u32 sceMp3LowLevelInit(u32 mp3, u32 unk) { @@ -701,7 +708,7 @@ const HLEFunction sceMp3[] = { {0XF5478233, &WrapI_U, "sceMp3ReleaseMp3Handle", 'i', "x" }, {0XAE6D2027, &WrapU_U, "sceMp3GetMPEGVersion", 'x', "x" }, {0X3548AEC8, &WrapU_U, "sceMp3GetFrameNum", 'i', "x" }, - {0X0840E808, &WrapU_UI, "sceMp3ResetPlayPositionByFrame", 'x', "xi" }, + {0X0840E808, &WrapU_UU, "sceMp3ResetPlayPositionByFrame", 'i', "xi" }, {0X1B839B83, &WrapU_UU, "sceMp3LowLevelInit", 'x', "xx" }, {0XE3EE2C81, &WrapU_UUUUU, "sceMp3LowLevelDecode", 'x', "xxxxx"} }; diff --git a/Core/HW/SimpleAudioDec.cpp b/Core/HW/SimpleAudioDec.cpp index ae1715a6ae..be35d16b20 100644 --- a/Core/HW/SimpleAudioDec.cpp +++ b/Core/HW/SimpleAudioDec.cpp @@ -474,8 +474,16 @@ u32 AuCtx::AuGetInfoToAddStreamData(u32 bufPtr, u32 sizePtr, u32 srcPosPtr) { return 0; } -u32 AuCtx::AuResetPlayPositionByFrame(int position) { - readPos = position; +u32 AuCtx::AuResetPlayPositionByFrame(int frame) { + // Note: this doesn't correctly handle padding or slot size, but the PSP doesn't either. + uint32_t bytesPerSecond = (MaxOutputSample / 8) * BitRate * 1000; + readPos = startPos + (frame * bytesPerSecond) / SamplingRate; + // Not sure why, but it seems to consistently seek 1 before, maybe in case it's off slightly. + if (frame != 0) + readPos -= 1; + SumDecodedSamples = frame * MaxOutputSample; + AuBufAvailable = 0; + sourcebuff.clear(); return 0; } From 848108c6ab0e078d8e93d4d6af974727dc9ef80b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 28 Apr 2019 13:15:35 -0700 Subject: [PATCH 6/6] Mp3: Set as inited only after success. --- Core/HLE/sceMp3.cpp | 53 ++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/Core/HLE/sceMp3.cpp b/Core/HLE/sceMp3.cpp index 3a58cb5cb0..b182ab54f6 100644 --- a/Core/HLE/sceMp3.cpp +++ b/Core/HLE/sceMp3.cpp @@ -393,17 +393,32 @@ static int sceMp3Init(u32 mp3) { return hleDelayResult(hleLogWarning(ME, ERROR_AVCODEC_INVALID_DATA, "no header found"), "mp3 init", PARSE_DELAY_MS); // Parse the Mp3 header - int layer = (header >> 17) & 0x3; - ctx->Version = ((header >> 19) & 0x3); - ctx->SamplingRate = __CalculateMp3SampleRates((header >> 10) & 0x3, ctx->Version); + int layerBits = (header >> 17) & 0x3; + int versionBits = (header >> 19) & 0x3; + ctx->SamplingRate = __CalculateMp3SampleRates((header >> 10) & 0x3, versionBits); ctx->Channels = __CalculateMp3Channels((header >> 6) & 0x3); - ctx->BitRate = __CalculateMp3Bitrates((header >> 12) & 0xF, ctx->Version, layer); - ctx->MaxOutputSample = CalculateMp3SamplesPerFrame(ctx->Version, layer); + ctx->BitRate = __CalculateMp3Bitrates((header >> 12) & 0xF, versionBits, layerBits); + ctx->MaxOutputSample = CalculateMp3SamplesPerFrame(versionBits, layerBits); ctx->freq = ctx->SamplingRate; - // for mp3, if required freq is 48000, reset resampling Frequency to 48000 seems get better sound quality (e.g. Miku Custom BGM) - if (ctx->freq == 48000) { - ctx->decoder->SetResampleFrequency(ctx->freq); + DEBUG_LOG(ME, "sceMp3Init(): channels=%i, samplerate=%iHz, bitrate=%ikbps", ctx->Channels, ctx->SamplingRate, ctx->BitRate); + + if (layerBits != 1) { + // TODO: Should return ERROR_AVCODEC_INVALID_DATA. + WARN_LOG_REPORT(ME, "sceMp3Init: invalid data: not layer 3"); + } + if (versionBits != 3) { + // TODO: Should return 0x80671301 (unsupported version?) + WARN_LOG_REPORT(ME, "sceMp3Init: invalid data: not MPEG v1"); + } + if (ctx->BitRate == 0 || ctx->BitRate == -1) { + return hleDelayResult(hleReportError(ME, ERROR_AVCODEC_INVALID_DATA, "invalid bitrate v%d l%d rate %04x", versionBits, layerBits, (header >> 12) & 0xF), "mp3 init", PARSE_DELAY_MS); + } + if (ctx->SamplingRate == -1) { + return hleDelayResult(hleReportError(ME, ERROR_AVCODEC_INVALID_DATA, "invalid sample rate v%d l%d rate %02x", versionBits, layerBits, (header >> 10) & 0x3), "mp3 init", PARSE_DELAY_MS); + } else if (ctx->SamplingRate != 44100) { + // TODO: Should return 0x80671302 (unsupported sample rate?) + WARN_LOG_REPORT(ME, "sceMp3Init: invalid data: not 44.1kHz"); } // Based on bitrate, we can calculate the frame size in bytes. @@ -413,24 +428,12 @@ static int sceMp3Init(u32 mp3) { uint64_t totalBytes = (ctx->endPos & 0xFFFFFFFF) - (ctx->startPos & 0xFFFFFFFF); ctx->FrameNum = (int)((totalBytes * ctx->SamplingRate) / bytesPerSecond); - DEBUG_LOG(ME, "sceMp3Init(): channels=%i, samplerate=%iHz, bitrate=%ikbps", ctx->Channels, ctx->SamplingRate, ctx->BitRate); + ctx->Version = versionBits; - if (layer != 1) { - // TODO: Should return ERROR_AVCODEC_INVALID_DATA. - WARN_LOG_REPORT(ME, "sceMp3Init: invalid data: not layer 3"); - } - if (ctx->Version != 3) { - // TODO: Should return 0x80671301 (unsupported version?) - WARN_LOG_REPORT(ME, "sceMp3Init: invalid data: not MPEG v1"); - } - if (ctx->BitRate == 0 || ctx->BitRate == -1) { - return hleDelayResult(hleReportError(ME, ERROR_AVCODEC_INVALID_DATA, "invalid bitrate v%d l%d rate %04x", ctx->Version, layer, (header >> 12) & 0xF), "mp3 init", PARSE_DELAY_MS); - } - if (ctx->SamplingRate == -1) { - return hleDelayResult(hleReportError(ME, ERROR_AVCODEC_INVALID_DATA, "invalid sample rate v%d l%d rate %02x", ctx->Version, layer, (header >> 10) & 0x3), "mp3 init", PARSE_DELAY_MS); - } else if (ctx->SamplingRate != 44100) { - // TODO: Should return 0x80671302 (unsupported sample rate?) - WARN_LOG_REPORT(ME, "sceMp3Init: invalid data: not 44.1kHz"); + // for mp3, if required freq is 48000, reset resampling Frequency to 48000 seems get better sound quality (e.g. Miku Custom BGM) + // TODO: Isn't this backwards? Woudln't we want to read as 48kHz and resample to 44.1kHz? + if (ctx->freq == 48000) { + ctx->decoder->SetResampleFrequency(ctx->freq); } return hleDelayResult(hleLogSuccessI(ME, 0), "mp3 init", PARSE_DELAY_MS);