mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 00:53:02 -05:00
172 lines
8.5 KiB
C++
172 lines
8.5 KiB
C++
#include "SceTypes.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
|
|
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 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*);
|
|
int APS5_VABI sceAjmBatchJobInitialize(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*);
|
|
int APS5_VABI sceAjmBatchJobRun(AjmBatchInfo*, std::uint32_t, std::uint64_t, const void*, std::size_t, void*, std::size_t, void*, std::size_t);
|
|
int APS5_VABI sceAjmBatchStart(std::uint32_t, const AjmBatchInfo*, int, AjmBatchError*, std::uint32_t*);
|
|
int APS5_VABI sceAjmBatchWait(std::uint32_t, std::uint32_t, std::uint32_t, AjmBatchError*);
|
|
}
|
|
|
|
namespace {
|
|
void Require(bool value, const char* message) {
|
|
if (!value) throw std::runtime_error(message);
|
|
}
|
|
|
|
void Submit(std::uint32_t context, const AjmBatchInfo& info) {
|
|
AjmBatchError error{};
|
|
std::uint32_t id = 0;
|
|
Require(sceAjmBatchStart(context, &info, 0, &error, &id) == 0 && sceAjmBatchWait(context, id, 0, &error) == 0, "batch submit");
|
|
}
|
|
|
|
std::array<std::uint8_t, 4> Config(unsigned channels) {
|
|
return {0xFE, static_cast<std::uint8_t>(channels == 1 ? 0x7C : 0x7E), 0x03, 0xF0};
|
|
}
|
|
|
|
std::vector<std::uint8_t> Packet(const std::vector<int>& amplitudes) {
|
|
std::vector<std::uint8_t> bytes;
|
|
std::size_t position = 0;
|
|
const auto put = [&](unsigned value, unsigned bits) {
|
|
for (unsigned bit = bits; bit > 0; --bit) {
|
|
if (position / 8 == bytes.size()) bytes.push_back(0);
|
|
bytes[position / 8] |= ((value >> (bit - 1)) & 1u) << (7 - position % 8);
|
|
++position;
|
|
}
|
|
};
|
|
for (unsigned frame = 0; frame < 4; ++frame) {
|
|
for (const auto amplitude : amplitudes) {
|
|
put(frame != 0, 1);
|
|
put(0, 1);
|
|
put(0, 1);
|
|
put(0, 4);
|
|
put(25, 5);
|
|
put(6, 3);
|
|
put(static_cast<unsigned>(amplitude) & 255u, 8);
|
|
put(static_cast<unsigned>(-amplitude) & 255u, 8);
|
|
while (position % 8 != 0) put(0, 1);
|
|
}
|
|
}
|
|
bytes.resize(128, 1);
|
|
return bytes;
|
|
}
|
|
|
|
struct Stream {
|
|
std::int32_t result = -1;
|
|
std::int32_t internalResult = -1;
|
|
std::int32_t consumed = -1;
|
|
std::int32_t produced = -1;
|
|
std::uint64_t samples = 0;
|
|
std::uint32_t frames = 0;
|
|
std::uint32_t reserved = 0;
|
|
};
|
|
|
|
template<class T>
|
|
std::vector<T> Decode(std::uint32_t context, unsigned channels, unsigned encoding, const std::vector<int>& amplitudes, unsigned profile = 7, bool partial = false, bool malformed = false) {
|
|
std::uint32_t instance = 0;
|
|
Require(sceAjmInstanceCreate(context, 1, static_cast<std::uint64_t>(encoding) << 7u, &instance) == 0, "create vibration instance");
|
|
auto config = Config(channels);
|
|
config[1] = static_cast<std::uint8_t>((profile << 4) | (config[1] & 15u));
|
|
std::vector<std::uint8_t> batch(4096);
|
|
AjmBatchInfo info{};
|
|
std::int32_t initialized[2] = {-1, -1};
|
|
Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0, "initialize batch");
|
|
Require(sceAjmBatchJobInitialize(&info, instance, config.data(), config.size(), initialized) == 0, "append initialize");
|
|
Submit(context, info);
|
|
Require(initialized[0] == 0 && initialized[1] == 0, "initialize vibration decoder");
|
|
auto packet = Packet(amplitudes);
|
|
const auto samples = profile == 1 ? 256 : profile == 4 ? 512 : 1024;
|
|
std::vector<T> pcm(samples * channels);
|
|
constexpr std::uint64_t flags = (1ull << 47) | (1ull << 12);
|
|
const auto run = [&](const std::vector<std::uint8_t>& input, std::size_t size) {
|
|
Stream stream;
|
|
Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0, "reset batch");
|
|
Require(sceAjmBatchJobRun(&info, instance, flags, input.data(), size, pcm.data(), pcm.size() * sizeof(T), &stream, sizeof(stream)) == 0, "append run");
|
|
Submit(context, info);
|
|
return stream;
|
|
};
|
|
if (partial) {
|
|
std::fill(pcm.begin(), pcm.end(), T{123});
|
|
const auto stream = run(packet, packet.size() - 1);
|
|
Require(stream.result == 8 && stream.consumed == 0 && stream.produced == 0, "partial superframe accounting");
|
|
Require(std::all_of(pcm.begin(), pcm.end(), [](T value) { return value == T{123}; }), "partial input changed PCM");
|
|
}
|
|
if (malformed) {
|
|
auto broken = packet;
|
|
broken[0] |= 0x80;
|
|
const auto stream = run(broken, broken.size());
|
|
Require((stream.result & 2) != 0 && stream.consumed == 0 && stream.produced == 0, "invalid first-frame flag");
|
|
}
|
|
const auto stream = run(packet, packet.size());
|
|
Require(stream.result == 0 && stream.internalResult == 0, "decode vibration superframe");
|
|
Require(stream.consumed == 128 && stream.produced == static_cast<int>(pcm.size() * sizeof(T)), "consumed/produced accounting");
|
|
Require(stream.samples == static_cast<unsigned>(samples) && stream.frames == 4, "decoded sample/frame accounting");
|
|
Require(sceAjmInstanceDestroy(context, instance) == 0, "destroy instance");
|
|
return pcm;
|
|
}
|
|
|
|
void Parse() {
|
|
for (const auto index : {6u, 7u}) {
|
|
for (const auto profile : {1u, 4u, 7u}) {
|
|
auto config = Config(index == 6 ? 1 : 2);
|
|
config[1] = static_cast<std::uint8_t>((profile << 4) | (index << 1));
|
|
AjmDecAt9ConfigDataInfo info{};
|
|
Require(sceAjmDecAt9ParseConfigData(config.data(), &info) == 0, "parse vibration config");
|
|
const unsigned samples = profile == 1 ? 64 : profile == 4 ? 128 : 256;
|
|
Require(info.channels == (index == 6 ? 1u : 2u) && info.sample_rate == (profile == 1 ? 12000u : profile == 4 ? 24000u : 48000u), "vibration layout/rate");
|
|
Require(info.superframe_size == 128 && info.frame_samples_per_channel == samples && info.superframe_samples_per_channel == samples * 4, "vibration frame geometry");
|
|
}
|
|
}
|
|
auto invalid = Config(2);
|
|
invalid[1] |= 1;
|
|
AjmDecAt9ConfigDataInfo info;
|
|
std::memset(&info, 0xAA, sizeof(info));
|
|
Require(sceAjmDecAt9ParseConfigData(invalid.data(), &info) == static_cast<int>(0x80930005), "invalid version accepted");
|
|
Require(info.superframe_size == 0xAAAAAAAAu, "invalid parse changed output");
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
Parse();
|
|
std::uint32_t context = 0;
|
|
Require(sceAjmInitialize(0, &context) == 0, "initialize AJM");
|
|
const auto left = Decode<float>(context, 1, 2, {1});
|
|
const auto right = Decode<float>(context, 1, 2, {2});
|
|
const auto stereo = Decode<float>(context, 2, 2, {1, 2}, 7, true, true);
|
|
Require(std::any_of(left.begin(), left.end(), [](float value) { return std::abs(value) > 0.000001f; }), "vibration decoded to silence");
|
|
for (std::size_t sample = 0; sample < left.size(); ++sample) {
|
|
Require(std::isfinite(stereo[2 * sample]) && std::isfinite(stereo[2 * sample + 1]), "non-finite PCM");
|
|
Require(stereo[2 * sample] == left[sample] && stereo[2 * sample + 1] == right[sample], "dual-mono interleaving or reset history");
|
|
}
|
|
const auto pcm16 = Decode<std::int16_t>(context, 2, 0, {1, 2});
|
|
const auto pcm32 = Decode<std::int32_t>(context, 2, 1, {1, 2});
|
|
for (std::size_t sample = 0; sample < stereo.size(); ++sample) {
|
|
Require(std::abs(static_cast<double>(pcm16[sample]) / 32768.0 - stereo[sample]) <= 1.0 / 32768.0, "PCM16 conversion");
|
|
Require(std::abs(static_cast<double>(pcm32[sample]) / 2147483648.0 - stereo[sample]) <= 1.0 / 2147483648.0, "PCM32 conversion");
|
|
}
|
|
for (const auto profile : {1u, 4u}) {
|
|
const auto mono = Decode<float>(context, 1, 2, {1}, profile);
|
|
const auto dual = Decode<float>(context, 2, 2, {1, 1}, profile);
|
|
Require(std::any_of(mono.begin(), mono.end(), [](float value) { return std::abs(value) > 0.000001f; }), "lower-rate vibration decoded to silence");
|
|
for (std::size_t sample = 0; sample < mono.size(); ++sample) Require(dual[2 * sample] == mono[sample] && dual[2 * sample + 1] == mono[sample], "lower-rate interleaving");
|
|
}
|
|
Require(sceAjmFinalize(context) == 0, "finalize AJM");
|
|
} catch (const std::exception& error) {
|
|
std::fprintf(stderr, "%s\n", error.what());
|
|
return 1;
|
|
}
|
|
}
|