feat(ajm): implement sceAjmDecMp3ParseFrame
Read the MPEG audio frame header and report the frame size, channel count, samples per channel, bitrate and sample rate. The signature, the result layout and the rate tables follow shadPS4's reimplementation of the PS4 library. Requesting the original file length throws: that lookup is not implemented. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
28e7799183
commit
f99be7e4d3
|
|
@ -389,6 +389,18 @@ struct AjmDecAt9ConfigDataInfo {
|
|||
std::uint32_t superframe_size;
|
||||
};
|
||||
|
||||
struct AjmDecMp3ParseFrame {
|
||||
std::uint64_t frame_size;
|
||||
std::uint32_t num_channels;
|
||||
std::uint32_t samples_per_channel;
|
||||
std::uint32_t bitrate;
|
||||
std::uint32_t sample_rate;
|
||||
std::uint32_t encoder_delay;
|
||||
std::uint32_t num_frames;
|
||||
std::uint32_t total_samples;
|
||||
std::uint32_t ofl_type;
|
||||
};
|
||||
|
||||
using AudioOut2ContextHandle = std::uint64_t;
|
||||
using AudioOut2PortHandle = std::uint64_t;
|
||||
using AudioOut2UserHandle = std::uintptr_t;
|
||||
|
|
|
|||
|
|
@ -129,9 +129,4 @@ const char* APS5_VABI sceAjmStrError(int error) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAjmDecMp3ParseFrame() {
|
||||
AjmStub(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -832,6 +832,36 @@ int APS5_VABI sceAjmDecAt9ParseConfigData(const void* config_data, AjmDecAt9Conf
|
|||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAjmDecMp3ParseFrame(const uint8_t* stream, uint32_t streamSize, int parseOfl, AjmDecMp3ParseFrame* frame) {
|
||||
if (!stream || streamSize < 4 || !frame) return SCE_AJM_ERROR_INVALID_PARAMETER;
|
||||
static constexpr std::uint32_t sampleRates[4][4] = {{11025, 12000, 8000, 0}, {0, 0, 0, 0}, {22050, 24000, 16000, 0}, {44100, 48000, 32000, 0}};
|
||||
static constexpr std::uint32_t kbps[4][16] = {
|
||||
{0, 8, 16, 24, 32, 40, 48, 56, 64, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0},
|
||||
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0},
|
||||
};
|
||||
const std::uint32_t header = (std::uint32_t{stream[0]} << 24u) | (std::uint32_t{stream[1]} << 16u) | (std::uint32_t{stream[2]} << 8u) | stream[3];
|
||||
const std::uint32_t version = (header >> 19u) & 3u;
|
||||
const std::uint32_t sampleRate = sampleRates[version][(header >> 10u) & 3u];
|
||||
const std::uint32_t bitrate = kbps[version][(header >> 12u) & 15u] * 1000u;
|
||||
const bool valid = (header >> 21u) == 0x7FFu && sampleRate != 0 && bitrate != 0;
|
||||
AJM_TRACE("[ajm] parse mp3 frame %02x %02x %02x %02x (%u bytes, parse ofl %d) -> %s, %u Hz, %u bps\n", stream[0], stream[1], stream[2], stream[3], streamSize, parseOfl, valid ? "ok" : "invalid", sampleRate, bitrate);
|
||||
if (!valid) return SCE_AJM_ERROR_INVALID_PARAMETER;
|
||||
if (parseOfl) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup)");
|
||||
const bool mpeg1 = version == 3;
|
||||
frame->frame_size = (mpeg1 ? 144u : 72u) * bitrate / sampleRate + ((header >> 9u) & 1u);
|
||||
frame->num_channels = ((header >> 6u) & 3u) == 3u ? 1u : 2u;
|
||||
frame->samples_per_channel = mpeg1 ? 1152u : 576u;
|
||||
frame->bitrate = bitrate;
|
||||
frame->sample_rate = sampleRate;
|
||||
frame->encoder_delay = 0;
|
||||
frame->num_frames = 0;
|
||||
frame->total_samples = 0;
|
||||
frame->ofl_type = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAjmBatchInitialize(void* buffer, size_t size, AjmBatchInfo* info) {
|
||||
if (!buffer || !info) return SCE_AJM_ERROR_INVALID_PARAMETER;
|
||||
info->p_buffer = buffer;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ extern "C" {
|
|||
int APS5_VABI sceAjmInitialize(std::int64_t, std::uint32_t*);
|
||||
int APS5_VABI sceAjmFinalize(std::uint32_t);
|
||||
int APS5_VABI sceAjmDecAt9ParseConfigData(const void*, AjmDecAt9ConfigDataInfo*);
|
||||
int APS5_VABI sceAjmDecMp3ParseFrame(const std::uint8_t*, std::uint32_t, int, AjmDecMp3ParseFrame*);
|
||||
int APS5_VABI sceAjmInstanceCreate(std::uint32_t, std::uint32_t, std::uint64_t, std::uint32_t*);
|
||||
int APS5_VABI sceAjmInstanceDestroy(std::uint32_t, std::uint32_t);
|
||||
int APS5_VABI sceAjmBatchInitialize(void*, std::size_t, AjmBatchInfo*);
|
||||
|
|
@ -148,6 +149,52 @@ void TestOpus(std::uint32_t context) {
|
|||
Require(sceAjmInstanceDestroy(context, instance) == 0);
|
||||
}
|
||||
|
||||
bool ParsesTo(const std::uint8_t* stream, std::uint32_t streamSize, std::uint64_t frameSize, std::uint32_t channels, std::uint32_t samples, std::uint32_t bitrate, std::uint32_t sampleRate) {
|
||||
AjmDecMp3ParseFrame frame;
|
||||
std::memset(&frame, 0xFF, sizeof(frame));
|
||||
if (sceAjmDecMp3ParseFrame(stream, streamSize, 0, &frame) != 0) return false;
|
||||
return frame.frame_size == frameSize && frame.num_channels == channels && frame.samples_per_channel == samples && frame.bitrate == bitrate && frame.sample_rate == sampleRate &&
|
||||
frame.encoder_delay == 0 && frame.num_frames == 0 && frame.total_samples == 0 && frame.ofl_type == 0;
|
||||
}
|
||||
|
||||
void TestMp3ParseFrame() {
|
||||
constexpr int invalidParameter = static_cast<int>(0x80930005);
|
||||
static_assert(sizeof(AjmDecMp3ParseFrame) == 40);
|
||||
for (std::size_t offset = 0; offset < sizeof(MP3_MONO); offset += 96) {
|
||||
Require(ParsesTo(MP3_MONO + offset, static_cast<std::uint32_t>(sizeof(MP3_MONO) - offset), 96, 1, 1152, 32000, 48000));
|
||||
}
|
||||
|
||||
const std::uint8_t mpeg1StereoPadded[4] = {0xFF, 0xFB, 0x92, 0x00};
|
||||
Require(ParsesTo(mpeg1StereoPadded, 4, 418, 2, 1152, 128000, 44100));
|
||||
const std::uint8_t mpeg1Protected[4] = {0xFF, 0xFA, 0xE8, 0x40};
|
||||
Require(ParsesTo(mpeg1Protected, 4, 1440, 2, 1152, 320000, 32000));
|
||||
const std::uint8_t mpeg2Mono[4] = {0xFF, 0xF3, 0x80, 0xC0};
|
||||
Require(ParsesTo(mpeg2Mono, 4, 208, 1, 576, 64000, 22050));
|
||||
const std::uint8_t mpeg2DualChannel[4] = {0xFF, 0xF3, 0xE8, 0x80};
|
||||
Require(ParsesTo(mpeg2DualChannel, 4, 720, 2, 576, 160000, 16000));
|
||||
const std::uint8_t mpeg25Mono[4] = {0xFF, 0xE3, 0x88, 0xC0};
|
||||
Require(ParsesTo(mpeg25Mono, 4, 576, 1, 576, 64000, 8000));
|
||||
const std::uint8_t mpeg25StereoPadded[4] = {0xFF, 0xE3, 0x12, 0x00};
|
||||
Require(ParsesTo(mpeg25StereoPadded, 4, 53, 2, 576, 8000, 11025));
|
||||
|
||||
AjmDecMp3ParseFrame frame{};
|
||||
Require(sceAjmDecMp3ParseFrame(nullptr, 4, 0, &frame) == invalidParameter);
|
||||
Require(sceAjmDecMp3ParseFrame(MP3_MONO, 4, 0, nullptr) == invalidParameter);
|
||||
Require(sceAjmDecMp3ParseFrame(MP3_MONO, 3, 0, &frame) == invalidParameter);
|
||||
const std::uint8_t brokenSync[4] = {0xFF, 0xDB, 0x14, 0xC4};
|
||||
Require(sceAjmDecMp3ParseFrame(brokenSync, 4, 0, &frame) == invalidParameter);
|
||||
const std::uint8_t reservedVersion[4] = {0xFF, 0xEB, 0x14, 0xC4};
|
||||
Require(sceAjmDecMp3ParseFrame(reservedVersion, 4, 0, &frame) == invalidParameter);
|
||||
const std::uint8_t freeBitrate[4] = {0xFF, 0xFB, 0x04, 0xC4};
|
||||
Require(sceAjmDecMp3ParseFrame(freeBitrate, 4, 0, &frame) == invalidParameter);
|
||||
const std::uint8_t forbiddenBitrate[4] = {0xFF, 0xFB, 0xF4, 0xC4};
|
||||
Require(sceAjmDecMp3ParseFrame(forbiddenBitrate, 4, 0, &frame) == invalidParameter);
|
||||
const std::uint8_t reservedSampleRate[4] = {0xFF, 0xFB, 0x1C, 0xC4};
|
||||
Require(sceAjmDecMp3ParseFrame(reservedSampleRate, 4, 0, &frame) == invalidParameter);
|
||||
const std::uint8_t mpeg25Above64Kbps[4] = {0xFF, 0xE3, 0x98, 0xC0};
|
||||
Require(sceAjmDecMp3ParseFrame(mpeg25Above64Kbps, 4, 0, &frame) == invalidParameter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
|
@ -164,6 +211,7 @@ int main() {
|
|||
const std::uint8_t badHeader[4] = {0xFD, 0x72, 0x1F, 0xF0};
|
||||
Require(sceAjmDecAt9ParseConfigData(badHeader, &info) == invalidParameter);
|
||||
Require(sceAjmDecAt9ParseConfigData(nullptr, &info) == invalidParameter);
|
||||
TestMp3ParseFrame();
|
||||
TestMp3(context);
|
||||
TestOpus(context);
|
||||
Require(sceAjmFinalize(context) == 0);
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceAvPlayerStartEx](../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - start info layout unknown; it is ignored and playback starts as with `sceAvPlayerStart`
|
||||
- [sceAvPlayerInit / sceAvPlayerInitEx](../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - behaviour without a memory replacement unknown; frame and sample buffers then come from the guest heap
|
||||
- [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info
|
||||
- [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps
|
||||
- [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted
|
||||
|
||||
### Functional
|
||||
|
|
@ -68,5 +69,6 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- `--to-intel` replaces the approximate VEX.128 register forms of VRSQRTPS and VRCPPS with [correctly rounded](../../core/relinker/codegen/src/x86/ReciprocalLowering.cpp) 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). The 256-bit, scalar, legacy and memory forms keep the native instruction, as does a 4-byte site with no movable instruction after it for the jump (5 sites in PPSA21564).
|
||||
- [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character.
|
||||
- [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture.
|
||||
- [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero): reading it from the frame's Xing/Info, VBRI or Fraunhofer data is not implemented.
|
||||
- [libScePngEnc](../../core/libs/prx/libScePngEnc/Export.cpp) honours `filter_type` only as all filters (adaptive) or a single filter: the [PNG encoder](../../core/Decoder/Png/src/Png.cpp) (stb) cannot restrict adaptive filtering to a subset, so the first filter in the mask is used.
|
||||
- [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified.
|
||||
|
|
|
|||
Loading…
Reference in New Issue