diff --git a/core/libs/prx/libSceAjm.native/Export.cpp b/core/libs/prx/libSceAjm.native/Export.cpp index 11bc7e1b7..7fe1d9502 100644 --- a/core/libs/prx/libSceAjm.native/Export.cpp +++ b/core/libs/prx/libSceAjm.native/Export.cpp @@ -33,18 +33,6 @@ int APS5_VABI sceAjmBatchJobControl(AjmBatchInfo* info, uint32_t instance, uint6 return 0; } -int APS5_VABI sceAjmBatchJobDecodeSingle(AjmBatchInfo* info, uint32_t instance, const void* bitstream_input, size_t bitstream_input_size, void* pcm_output, size_t pcm_output_size, void* result) { - (void)info; - (void)instance; - (void)bitstream_input; - (void)bitstream_input_size; - (void)pcm_output; - (void)pcm_output_size; - (void)result; - AjmStub(__func__); - return 0; -} - int APS5_VABI sceAjmBatchJobDecodeSplit(AjmBatchInfo* info, uint32_t instance, const AjmBuffer* input_buffers, size_t input_buffers_num, const AjmBuffer* output_buffers, size_t output_buffers_num, void* result) { (void)info; (void)instance; @@ -69,23 +57,6 @@ int APS5_VABI sceAjmBatchJobEncode(AjmBatchInfo* info, uint32_t instance, const return 0; } -int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo* info, uint32_t instance, void* result, size_t result_size) { - (void)info; - (void)instance; - (void)result; - (void)result_size; - AjmStub(__func__); - return 0; -} - -int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo* info, uint32_t instance, void* result) { - (void)info; - (void)instance; - (void)result; - AjmStub(__func__); - return 0; -} - int APS5_VABI sceAjmBatchJobGetInfo(AjmBatchInfo* info, uint32_t instance, void* result) { (void)info; (void)instance; diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 5678a519b..50cbee420 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -51,6 +51,7 @@ bool TraceEnabled() { if (TraceEnabled()) std::fprintf(stderr, __VA_ARGS__); \ } while (0) +constexpr std::uint64_t RUN_GET_CODEC_INFO = 1ull << 11; constexpr std::uint64_t RUN_MULTIPLE_FRAMES = 1ull << 12; constexpr std::uint64_t SIDEBAND_GAPLESS_DECODE = 1ull << 45; constexpr std::uint64_t SIDEBAND_FORMAT = 1ull << 46; @@ -87,6 +88,13 @@ struct SidebandMultipleFrames { std::uint32_t reserved; }; +struct SidebandAt9CodecInfo { + std::uint32_t superframeSize; + std::uint32_t framesInSuperframe; + std::uint32_t nextFrameSize; + std::uint32_t frameSamples; +}; + struct Instance { std::uint32_t codec = 0; std::uint64_t flags = 0; @@ -337,6 +345,20 @@ void EmitFrame(Instance& instance, PcmOutputs& outputs, const std::uint8_t* pcm, instance.totalDecodedSamples += count; } +SidebandFormat CurrentFormat(const Instance& instance) { + switch (instance.codec) { + case CODEC_AT9: return {static_cast(instance.info.channels), ChannelMask(static_cast(instance.info.channels)), static_cast(instance.info.samplingRate), PcmEncoding(instance), 0, 0}; + case CODEC_MP3: return {instance.mp3Channels, ChannelMask(instance.mp3Channels), instance.mp3SampleRate, PcmEncoding(instance), instance.mp3Bitrate, 0}; + case CODEC_OPUS: return {instance.opusChannels, ChannelMask(instance.opusChannels), instance.opusSampleRate, PcmEncoding(instance), 0, 0}; + default: throw std::runtime_error("AJM: the format of codec " + std::to_string(instance.codec) + " is not implemented"); + } +} + +SidebandAt9CodecInfo At9CodecInfo(const Instance& instance) { + const auto superframeSize = static_cast(instance.info.superframeSize); + return {superframeSize, static_cast(instance.info.framesInSuperframe), instance.superframeRemaining == 0 ? superframeSize : instance.superframeRemaining, static_cast(instance.info.frameSamples)}; +} + void WriteRunSideband(const JobHeader& job, const Instance& instance, std::int32_t result, std::size_t consumed, std::size_t produced, std::uint32_t frames, const SidebandFormat& format) { auto* sideband = static_cast(job.sideband); std::size_t offset = 0; @@ -353,6 +375,10 @@ void WriteRunSideband(const JobHeader& job, const Instance& instance, std::int32 } if (job.flags & SIDEBAND_FORMAT) write(&format, sizeof(format)); if (job.flags & SIDEBAND_GAPLESS_DECODE) write(&instance.gapless, sizeof(instance.gapless)); + if (job.flags & RUN_GET_CODEC_INFO) { + const SidebandAt9CodecInfo codecInfo = At9CodecInfo(instance); + write(&codecInfo, sizeof(codecInfo)); + } if (job.flags & RUN_MULTIPLE_FRAMES) { const SidebandMultipleFrames multiple{frames, 0}; write(&multiple, sizeof(multiple)); @@ -431,7 +457,7 @@ void RunAt9(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, c for (std::size_t i = 0; i < input.size() && i < 16; ++i) std::fprintf(stderr, " %02x", input[i]); std::fprintf(stderr, "\n"); } - WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, SidebandFormat{static_cast(channels), ChannelMask(channels), static_cast(instance.info.samplingRate), encoding, 0, 0}); + WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, CurrentFormat(instance)); } struct Mp3Frame { @@ -546,7 +572,7 @@ void RunMp3(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, c AJM_TRACE("[ajm] instance %u mp3 run flags 0x%llx: %zu input bytes, %zu output bytes -> result 0x%x, %u frames, consumed %zu, produced %zu, %u channels, %u Hz, %u bps, total samples %llu\n", job.instance, static_cast(job.flags), input.size(), pcmOutputs.capacity, static_cast(result), frames, consumed, pcmOutputs.produced, instance.mp3Channels, instance.mp3SampleRate, instance.mp3Bitrate, static_cast(instance.totalDecodedSamples)); - WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, SidebandFormat{instance.mp3Channels, ChannelMask(instance.mp3Channels), instance.mp3SampleRate, encoding, instance.mp3Bitrate, 0}); + WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, CurrentFormat(instance)); } void OpenOpus(Instance& instance) { static const bool quiet = (av_log_set_level(TraceEnabled() ? AV_LOG_WARNING : AV_LOG_QUIET), true); @@ -682,7 +708,7 @@ void RunOpus(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, if (consumed == input.size() && frames == 0 && pcmOutputs.produced == 0 && result == 0) result |= AJM_RESULT_PARTIAL_INPUT; AJM_TRACE("[ajm] instance %u opus run flags 0x%llx: %zu input bytes, %zu output bytes -> result 0x%x, %u frames, consumed %zu, produced %zu, total samples %llu\n", job.instance, static_cast(job.flags), input.size(), pcmOutputs.capacity, static_cast(result), frames, consumed, pcmOutputs.produced, static_cast(instance.totalDecodedSamples)); - WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, SidebandFormat{instance.opusChannels, ChannelMask(instance.opusChannels), instance.opusSampleRate, encoding, 0, 0}); + WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, CurrentFormat(instance)); } void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* outputs) { @@ -729,14 +755,21 @@ void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* out if (!instance->initialized) { AJM_TRACE("[ajm] instance %u run before initialize\n", job.instance); WriteResult(job.sideband, job.sidebandSize, AJM_RESULT_NOT_INITIALIZED); + } else if (instance->codec != CODEC_AT9 && instance->codec != CODEC_MP3 && instance->codec != CODEC_OPUS) { + throw std::runtime_error("AJM: decoding codec " + std::to_string(instance->codec) + " is not implemented"); + } else if ((job.flags & RUN_GET_CODEC_INFO) && (job.flags & RUN_MULTIPLE_FRAMES)) { + NotImplemented_nid_no_patch("AJM run job with both RUN_GET_CODEC_INFO and RUN_MULTIPLE_FRAMES (sideband order)"); + } else if ((job.flags & RUN_GET_CODEC_INFO) && instance->codec != CODEC_AT9) { + NotImplemented_nid_no_patch("AJM RUN_GET_CODEC_INFO for a codec other than ATRAC9"); + } else if (job.inputCount == 0 && job.outputCount == 0) { + AJM_TRACE("[ajm] instance %u run flags 0x%llx without buffers, sideband %llu bytes\n", job.instance, static_cast(job.flags), static_cast(job.sidebandSize)); + WriteRunSideband(job, *instance, 0, 0, 0, 0, CurrentFormat(*instance)); } else if (instance->codec == CODEC_AT9) { RunAt9(*instance, job, inputs, outputs); } else if (instance->codec == CODEC_MP3) { RunMp3(*instance, job, inputs, outputs); - } else if (instance->codec == CODEC_OPUS) { - RunOpus(*instance, job, inputs, outputs); } else { - throw std::runtime_error("AJM: decoding codec " + std::to_string(instance->codec) + " is not implemented"); + RunOpus(*instance, job, inputs, outputs); } break; default: @@ -907,6 +940,18 @@ int APS5_VABI sceAjmBatchJobDecode(AjmBatchInfo* info, uint32_t instance, const return sceAjmBatchJobRun(info, instance, SIDEBAND_STREAM, bitstream_input, bitstream_input_size, pcm_output, pcm_output_size, result, sizeof(SidebandResult) + sizeof(SidebandStream)); } +int APS5_VABI sceAjmBatchJobDecodeSingle(AjmBatchInfo* info, uint32_t instance, const void* bitstream_input, size_t bitstream_input_size, void* pcm_output, size_t pcm_output_size, void* result) { + return sceAjmBatchJobRun(info, instance, SIDEBAND_STREAM, bitstream_input, bitstream_input_size, pcm_output, pcm_output_size, result, sizeof(SidebandResult) + sizeof(SidebandStream)); +} + +int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo* info, uint32_t instance, void* result) { + return sceAjmBatchJobRunSplit(info, instance, SIDEBAND_GAPLESS_DECODE, nullptr, 0, nullptr, 0, result, sizeof(SidebandResult) + sizeof(SidebandGaplessDecode)); +} + +int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo* info, uint32_t instance, void* result, size_t result_size) { + return sceAjmBatchJobRunSplit(info, instance, RUN_GET_CODEC_INFO, nullptr, 0, nullptr, 0, result, result_size); +} + int APS5_VABI sceAjmBatchJobGetStatistics(AjmBatchInfo* info, float interval, void* result) { (void)interval; return Append(info, MakeHeader(JobKind::GetStatistics, 0, result, 24), nullptr, nullptr); diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index 590e6b9ab..8d8504257 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include extern "C" { @@ -17,6 +18,11 @@ 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 sceAjmBatchJobDecode(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*, std::size_t, void*); +int APS5_VABI sceAjmBatchJobDecodeSingle(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, 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 sceAjmBatchJobSetGaplessDecode(AjmBatchInfo*, std::uint32_t, const void*, int, void*); +int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo*, std::uint32_t, void*); +int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo*, std::uint32_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*); } @@ -195,6 +201,126 @@ void TestMp3ParseFrame() { Require(sceAjmDecMp3ParseFrame(mpeg25Above64Kbps, 4, 0, &frame) == invalidParameter); } +struct GaplessDecode { + std::uint32_t totalSamples; + std::uint16_t skipSamples; + std::uint16_t skippedSamples; +}; + +struct GaplessSideband { + std::int32_t result; + std::int32_t internalResult; + std::uint32_t totalSamples; + std::uint16_t skipSamples; + std::uint16_t skippedSamples; +}; + +struct At9CodecInfoSideband { + std::int32_t result; + std::int32_t internalResult; + std::uint32_t superframeSize; + std::uint32_t framesInSuperframe; + std::uint32_t nextFrameSize; + std::uint32_t frameSamples; +}; + +void Submit(std::uint32_t context, const AjmBatchInfo& info) { + std::uint32_t id = 0; + AjmBatchError error{}; + Require(sceAjmBatchStart(context, &info, 0, &error, &id) == 0 && sceAjmBatchWait(context, id, 0, &error) == 0); +} + +bool Refused(std::uint32_t context, const AjmBatchInfo& info) { + std::uint32_t id = 0; + AjmBatchError error{}; + try { + static_cast(sceAjmBatchStart(context, &info, 0, &error, &id)); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void TestDecodeSingle(std::uint32_t context) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0); + std::vector batch(0x40); + std::vector pcm(1152 * 6); + AjmBatchInfo info{}; + DecodeSideband sideband{}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobDecodeSingle(&info, instance, MP3_MONO, sizeof(MP3_MONO), pcm.data(), pcm.size() * sizeof(std::int16_t), &sideband) == 0); + Submit(context, info); + Require(sideband.result == 0 && sideband.inputConsumed == 96 && sideband.outputWritten == 1152 * 2 && sideband.totalDecodedSamples == 1152); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +void TestGaplessDecode(std::uint32_t context) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0); + std::vector batch(4096); + const GaplessDecode gapless{2000, 100, 0}; + AjmBatchInfo info{}; + std::int32_t setResult[2] = {-1, -1}; + GaplessSideband before{-1, -1, 0, 0, 0xffff}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobSetGaplessDecode(&info, instance, &gapless, 1, setResult) == 0); + Require(sceAjmBatchJobGetGaplessDecode(&info, instance, &before) == 0); + Submit(context, info); + Require(setResult[0] == 0); + Require(before.result == 0 && before.internalResult == 0 && before.totalSamples == 2000 && before.skipSamples == 100 && before.skippedSamples == 0); + + std::vector pcm(1152); + DecodeSideband decoded{}; + GaplessSideband after{-1, -1, 0, 0, 0}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobDecodeSingle(&info, instance, MP3_MONO, sizeof(MP3_MONO), pcm.data(), pcm.size() * sizeof(std::int16_t), &decoded) == 0); + Require(sceAjmBatchJobGetGaplessDecode(&info, instance, &after) == 0); + Submit(context, info); + Require(decoded.result == 0 && decoded.inputConsumed == 96 && decoded.outputWritten == (1152 - 100) * 2); + Require(after.result == 0 && after.skippedSamples == 100); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +void TestCodecInfo(std::uint32_t context) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 1, 0, &instance) == 0); + std::vector batch(4096); + AjmBatchInfo info{}; + At9CodecInfoSideband early{-1, -1, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, &early, sizeof(early)) == 0); + Submit(context, info); + Require(early.result == 1 && early.superframeSize == 0xaaaaaaaau); + + const std::uint8_t config[8] = {0xFE, 0x72, 0x1F, 0xF0}; + std::int32_t initResult[2] = {-1, -1}; + At9CodecInfoSideband codec{-1, -1, 0, 0, 0, 0}; + At9CodecInfoSideband bounded{-1, -1, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobInitialize(&info, instance, config, sizeof(config), initResult) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, &codec, sizeof(codec)) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, &bounded, 2 * sizeof(std::int32_t)) == 0); + Submit(context, info); + Require(initResult[0] == 0); + Require(codec.result == 0 && codec.internalResult == 0 && codec.superframeSize == 1024 && codec.framesInSuperframe == 4 && codec.nextFrameSize == 1024 && codec.frameSamples == 256); + Require(bounded.result == 0 && bounded.superframeSize == 0xaaaaaaaau && bounded.frameSamples == 0xaaaaaaaau); + + constexpr std::uint64_t runGetCodecInfo = 1ull << 11; + constexpr std::uint64_t runMultipleFrames = 1ull << 12; + std::uint8_t sideband[64] = {}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobRun(&info, instance, runGetCodecInfo | runMultipleFrames, nullptr, 0, nullptr, 0, sideband, sizeof(sideband)) == 0); + Require(Refused(context, info)); + Require(sceAjmInstanceDestroy(context, instance) == 0); + + Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0); + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, sideband, sizeof(sideband)) == 0); + Require(Refused(context, info)); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + } int main() { @@ -214,5 +340,8 @@ int main() { TestMp3ParseFrame(); TestMp3(context); TestOpus(context); + TestDecodeSingle(context); + TestGaplessDecode(context); + TestCodecInfo(context); Require(sceAjmFinalize(context) == 0); }