Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions core/libs/prx/libSceAjm.native/src/Ajm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ struct SidebandResampleInfo {

struct Resampler {
float ratio = 1.0f;
float change = 0.0f;
std::vector<double> frames;
double position = 1.0;
};
Expand Down Expand Up @@ -542,7 +543,7 @@ void StorePcm(double value, std::uint8_t* out, std::uint32_t encoding) {
}

bool Resampling(const Instance& instance) {
return instance.resampler.ratio != 1.0f || !instance.resampler.frames.empty();
return instance.resampler.ratio != 1.0f || instance.resampler.change != 0.0f || !instance.resampler.frames.empty();
}

std::size_t ResamplerHeld(const Instance& instance, std::size_t channels) {
Expand Down Expand Up @@ -580,6 +581,9 @@ void ResamplerProduce(Instance& instance, PcmOutputs& outputs, std::size_t chann
}
outputs.Emit(frame.data(), frameBytes);
resampler.position += resampler.ratio;
if (resampler.change != 0.0f) {
resampler.ratio = std::max(0.0f, resampler.ratio + resampler.change);
}
}
const auto index = std::min(static_cast<std::size_t>(resampler.position), available);
if (index > 1) {
Expand Down Expand Up @@ -1424,11 +1428,15 @@ void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* out
case JobKind::Control:
Control(*instance, job, inputs);
break;
case JobKind::SetResampleParameters:
std::memcpy(&instance->resampler.ratio, job.parameters, sizeof(instance->resampler.ratio));
AJM_TRACE("[ajm] instance %u set resample ratio %f\n", job.instance, static_cast<double>(instance->resampler.ratio));
case JobKind::SetResampleParameters: {
float params[2] = {1.0f, 0.0f};
std::memcpy(params, job.parameters, std::min<std::size_t>(job.parameterSize, sizeof(params)));
instance->resampler.ratio = params[0];
instance->resampler.change = job.parameterSize >= sizeof(float) * 2 ? params[1] : 0.0f;
AJM_TRACE("[ajm] instance %u set resample ratio %f change %f\n", job.instance, static_cast<double>(instance->resampler.ratio), static_cast<double>(instance->resampler.change));
WriteResult(job.sideband, job.sidebandSize, 0);
break;
}
case JobKind::GetResampleInfo: {
const SidebandResampleInfo resample{instance->resampler.ratio, static_cast<std::int32_t>(HeldSamples(*instance)), {}};
WriteResult(job.sideband, job.sidebandSize, 0);
Expand Down Expand Up @@ -1617,8 +1625,13 @@ int APS5_VABI sceAjmBatchJobSetResampleParameters(AjmBatchInfo* info, uint32_t i
}

int APS5_VABI sceAjmBatchJobSetResampleParametersEx(AjmBatchInfo* info, uint32_t instance, float ratio_start, float ratio_change_per_sample, uint32_t flags, void* result) {
if (ratio_change_per_sample != 0.0f) throw std::runtime_error("sceAjmBatchJobSetResampleParametersEx: a ratio change per sample is unsupported");
return sceAjmBatchJobSetResampleParameters(info, instance, ratio_start, flags, result);
(void)flags;
if (!std::isfinite(ratio_start) || ratio_start <= 0.0f || !std::isfinite(ratio_change_per_sample)) return SCE_AJM_ERROR_INVALID_PARAMETER;
auto header = MakeHeader(JobKind::SetResampleParameters, instance, result, sizeof(SidebandResult));
const float parameters[2] = {ratio_start, ratio_change_per_sample};
std::memcpy(header.parameters, parameters, sizeof(parameters));
header.parameterSize = sizeof(parameters);
return Append(info, header, nullptr, nullptr);
}

int APS5_VABI sceAjmBatchJobGetResampleInfo(AjmBatchInfo* info, uint32_t instance, void* result) {
Expand Down
25 changes: 18 additions & 7 deletions core/libs/tests/GuestAjm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1462,14 +1462,25 @@ void TestResampleParametersEx(std::uint32_t context) {
std::int64_t result[2] = {-1, -1};
Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0);
for (const float bad : {0.0f, -1.0f, std::nanf(""), INFINITY}) Require(sceAjmBatchJobSetResampleParametersEx(&info, extended, bad, 0.0f, 1, result) == static_cast<int>(0x80930005));
bool rampThrows = false;
try {
static_cast<void>(sceAjmBatchJobSetResampleParametersEx(&info, extended, 1.0f, 0.001f, 1, result));
} catch (const std::runtime_error&) {
rampThrows = true;
for (const float bad : {std::nanf(""), INFINITY, -INFINITY}) Require(sceAjmBatchJobSetResampleParametersEx(&info, extended, 1.0f, bad, 1, result) == static_cast<int>(0x80930005));

std::uint32_t ramped = 0;
Require(sceAjmInstanceCreate(context, 0, 0, &ramped) == 0);
std::vector<std::int16_t> rampedPcm(512);
{
std::vector<std::uint8_t> rampBatch(4096);
AjmBatchInfo rampInfo{};
std::int64_t setResult[2] = {-1, -1};
DecodeSideband sideband{};
Require(sceAjmBatchInitialize(rampBatch.data(), rampBatch.size(), &rampInfo) == 0);
Require(sceAjmBatchJobSetResampleParametersEx(&rampInfo, ramped, 1.0f, 0.001f, 1, setResult) == 0);
Require(sceAjmBatchJobDecode(&rampInfo, ramped, MP3_MONO, sizeof(MP3_MONO), rampedPcm.data(), rampedPcm.size() * sizeof(std::int16_t), &sideband) == 0);
Submit(context, rampInfo);
Require(setResult[0] == 0 && sideband.result == 0 && sideband.outputWritten == 512 * 2);
}
Require(rampThrows && info.offset == 0);
Require(sceAjmInstanceDestroy(context, plain) == 0 && sceAjmInstanceDestroy(context, extended) == 0);
const auto resampleInfo = GetResampleInfo(context, ramped);
Require(std::abs(resampleInfo.ratio - 1.512f) < 0.001f && rampedPcm != plainPcm);
Require(sceAjmInstanceDestroy(context, plain) == 0 && sceAjmInstanceDestroy(context, extended) == 0 && sceAjmInstanceDestroy(context, ramped) == 0);
}

const std::uint8_t AT9_MONO_SILENT_SUPERFRAME[] = {
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/debt/libSceAjm.native.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- [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; the original file length (`parse_ofl`) layout (Xing/Info + LAME tag, VBRI, Fraunhofer `0xB4` block with its CRC, encoder delay = samples per frame + LAME delay + 529) also comes from shadPS4 only, and the Fraunhofer block has no other public description
- [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts
- [AJM Opus decoder](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout and little-endian `u16` packet byte count come from The Smurfs Dreams (`2, 48000, 0`) and Lost Soul Aside (`4, 48000, 1`). The third word is interpreted as the mapping family, as in [KytyPS5's initialize structure](https://github.com/KytyPS5/KytyPS5/blob/984cfcebd141b211aa9e0a8cde63b0ebe652d07a/src/libs/ajm/opus_decoder.h#L11-L15). Family 1 uses the conventional stream counts, coupled counts and channel map from [libopus 1.5.2's surround layout](https://github.com/xiph/opus/blob/v1.5.2/src/opus_multistream_encoder.c#L47-L55), passed to FFmpeg through an [RFC 7845 section 5.1](https://datatracker.ietf.org/doc/html/rfc7845#section-5.1) header with zero pre-skip and gain; gapless skipping remains controlled by AJM sideband parameters. FFmpeg returns PCM in its native speaker order. The implicit stream layout, PCM channel order and invalid family-0 channel count result are not verified on a console. NHL 27 (PPSA34063) passes `1, 48000, 255`. Family 255 with one channel is decoded as one mono stream, the same as family 0; this is assumed, since the stream count, coupled stream count and channel mapping that a family-255 Opus header carries ([RFC 7845 section 5.1.1](https://datatracker.ietf.org/doc/html/rfc7845#section-5.1.1)) are not passed. Only 48000 Hz, mapping families 0 and 1, and family 255 with one channel are implemented; other rates and families, and family 255 with more channels, throw
- [sceAjmBatchJobSetResampleParametersEx](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `(info, instance, float ratio_start, float ratio_change_per_sample, flags, result)` signature is inferred from the name and from `sceAjmBatchJobSetResampleParameters`; with a zero change per sample it sets the ratio as `sceAjmBatchJobSetResampleParameters` does (Watch Dogs: Legion, PPSA01500, calls it so). How a non-zero change ramps the ratio is unknown and throws
- [sceAjmBatchJobSetResampleParametersEx](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `(info, instance, float ratio_start, float ratio_change_per_sample, flags, result)` signature is inferred from the name and from `sceAjmBatchJobSetResampleParameters`; with a zero change per sample it sets the ratio as `sceAjmBatchJobSetResampleParameters` does (Watch Dogs: Legion, PPSA01500, calls it so). How a non-zero change ramps the ratio is not confirmed on a console (no source: KytyPS5 ignores it, prosper stubs it, the PS5 firmware does not export the batch builders to a payload): it is assumed to be added to the ratio after each emitted output frame, clamped at zero, and kept across batches. RIDE 4 (PPSA01599) passes one call with a change of 4.6e-7, so the exact rule matters little there.
- [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET (bit 13), INITIALIZE (bit 14) and the sideband input order (gapless decode, then initialize parameters) come from [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4); other flags throw
- [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET is assumed to clear the context as sceAjmBatchJobClearContext does
- [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - initialize parameters are 8 bytes for ATRAC9 and M4AAC, 12 for Opus and none for MP3, as one title passes them; other codecs and sizes, and an output other than the 8-byte result, throw
Expand Down
Loading