diff --git a/rpcsx/CMakeLists.txt b/rpcsx/CMakeLists.txt index 57309413c..bf5b64658 100644 --- a/rpcsx/CMakeLists.txt +++ b/rpcsx/CMakeLists.txt @@ -9,6 +9,8 @@ add_library(orbis::kernel::config ALIAS standalone-config) add_executable(rpcsx audio/AudioDevice.cpp audio/AlsaDevice.cpp + audio/At9Codec.cpp + audio/FFmpegCodecs.cpp iodev/ajm.cpp iodev/blockpool.cpp diff --git a/rpcsx/audio/At9Codec.cpp b/rpcsx/audio/At9Codec.cpp new file mode 100644 index 000000000..66fcd36cb --- /dev/null +++ b/rpcsx/audio/At9Codec.cpp @@ -0,0 +1,54 @@ +#include "../iodev/ajm.hpp" +#include + +extern "C" { +#include +#include +} + +struct At9CodecInfoSideband { + orbis::uint32_t superFrameSize; + orbis::uint32_t framesInSuperFrame; + orbis::uint32_t unk0; + orbis::uint32_t frameSamples; +}; + +struct At9CodecInstance : ajm::CodecInstance { + std::vector inputBuffer; + std::vector outputBuffer; + + orbis::ptr handle{}; + orbis::uint32_t inputChannels{}; + orbis::uint32_t framesInSuperframe{}; + orbis::uint32_t frameSamples{}; + orbis::uint32_t superFrameDataLeft{}; + orbis::uint32_t superFrameDataIdx{}; + orbis::uint32_t superFrameSize{}; + orbis::uint32_t estimatedSizeUsed{}; + orbis::uint32_t sampleRate{}; + Atrac9Format outputFormat{}; + orbis::uint32_t configData{}; + + ~At9CodecInstance() { + if (handle) { + Atrac9ReleaseHandle(handle); + } + } + + std::uint64_t runJob(const ajm::Job &job) override {} + void reset() override {} +}; + +struct At9Codec : ajm::Codec { + orbis::ErrorCode createInstance(orbis::Ref *instance, + std::uint32_t unk0, + std::uint64_t flags) override { + auto at9 = orbis::Ref{orbis::knew()}; + *instance = at9; + return {}; + } +}; + +void createAt9Codec(AjmDevice *ajm) { + ajm->createCodec(ajm::CodecId::At9); +} diff --git a/rpcsx/audio/At9Codec.hpp b/rpcsx/audio/At9Codec.hpp new file mode 100644 index 000000000..0bac343a0 --- /dev/null +++ b/rpcsx/audio/At9Codec.hpp @@ -0,0 +1,3 @@ +#pragma once + +void createAt9Codec(class AjmDevice *ajm); diff --git a/rpcsx/audio/FFmpegCodecs.cpp b/rpcsx/audio/FFmpegCodecs.cpp new file mode 100644 index 000000000..9796644e2 --- /dev/null +++ b/rpcsx/audio/FFmpegCodecs.cpp @@ -0,0 +1,216 @@ +#include "../iodev/ajm.hpp" + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +} + +// Thanks to mystical SirNickity with 1 post +// https://hydrogenaud.io/index.php?topic=85125.msg747716#msg747716 + +inline constexpr uint8_t mpeg_versions[4] = {25, 0, 2, 1}; + +// Layers - use [layer] +inline constexpr uint8_t mpeg_layers[4] = {0, 3, 2, 1}; + +// Bitrates - use [version][layer][bitrate] +inline constexpr uint16_t mpeg_bitrates[4][4][16] = { + { + // Version 2.5 + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, + 0}, // Layer 3 + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, + 0}, // Layer 2 + {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, + 0} // Layer 1 + }, + { + // Reserved + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Invalid + }, + { + // Version 2 + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, + 0}, // Layer 3 + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, + 0}, // Layer 2 + {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, + 0} // Layer 1 + }, + { + // Version 1 + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, + 0}, // Layer 3 + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, + 0}, // Layer 2 + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, + 0}, // Layer 1 + }}; + +// Sample rates - use [version][srate] +inline constexpr uint16_t mpeg_srates[4][4] = { + {11025, 12000, 8000, 0}, // MPEG 2.5 + {0, 0, 0, 0}, // Reserved + {22050, 24000, 16000, 0}, // MPEG 2 + {44100, 48000, 32000, 0} // MPEG 1 +}; + +// Samples per frame - use [version][layer] +inline constexpr uint16_t mpeg_frame_samples[4][4] = { + // Rsvd 3 2 1 < Layer v Version + {0, 576, 1152, 384}, // 2.5 + {0, 0, 0, 0}, // Reserved + {0, 576, 1152, 384}, // 2 + {0, 1152, 1152, 384} // 1 +}; + +// Slot size (MPEG unit of measurement) - use [layer] +inline constexpr uint8_t mpeg_slot_size[4] = {0, 1, 1, 4}; // Rsvd, 3, 2, 1 + +constexpr uint32_t get_mp3_data_size(const uint8_t *data) { + // Quick validity check + if (((data[0] & 0xFF) != 0xFF) || ((data[1] & 0xE0) != 0xE0) // 3 sync bits + || ((data[1] & 0x18) == 0x08) // Version rsvd + || ((data[1] & 0x06) == 0x00) // Layer rsvd + || ((data[2] & 0xF0) == 0xF0) // Bitrate rsvd + ) { + return 0; + } + + // Data to be extracted from the header + uint8_t ver = (data[1] & 0x18) >> 3; // Version index + uint8_t lyr = (data[1] & 0x06) >> 1; // Layer index + uint8_t pad = (data[2] & 0x02) >> 1; // Padding? 0/1 + uint8_t brx = (data[2] & 0xf0) >> 4; // Bitrate index + uint8_t srx = (data[2] & 0x0c) >> 2; // SampRate index + + // Lookup real values of these fields + uint32_t bitrate = mpeg_bitrates[ver][lyr][brx] * 1000; + uint32_t samprate = mpeg_srates[ver][srx]; + uint16_t samples = mpeg_frame_samples[ver][lyr]; + uint8_t slot_size = mpeg_slot_size[lyr]; + + // In-between calculations + float bps = static_cast(samples) / 8.0f; + float fsize = + ((bps * static_cast(bitrate)) / static_cast(samprate)) + + ((pad) ? slot_size : 0); + + // ORBIS_LOG_TODO(__FUNCTION__, (uint16_t)ver, (uint16_t)lyr, + // (uint16_t)pad, (uint16_t)brx, (uint16_t)srx, bitrate, + // samprate, samples, (uint16_t)slot_size, bps, fsize, + // static_cast(fsize)); + + // Frame sizes are truncated integers + return static_cast(fsize); +} + +inline constexpr orbis::uint32_t kAACFreq[12] = {96000, 88200, 64000, 48000, + 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000}; + +struct MP3CodecInfoSideband { + orbis::uint32_t header; + orbis::uint8_t unk0; + orbis::uint8_t unk1; + orbis::uint8_t unk2; + orbis::uint8_t unk3; + orbis::uint8_t unk4; + orbis::uint8_t unk5; + orbis::uint16_t unk6; + orbis::uint16_t unk7; + orbis::uint16_t unk8; +}; + +struct AACCodecInfoSideband { + orbis::uint32_t heaac; + orbis::uint32_t unk0; +}; + +enum AACHeaderType { AAC_ADTS = 1, AAC_RAW = 2 }; + +static AVSampleFormat ajmToAvFormat(ajm::Format ajmFormat) { + switch (ajmFormat) { + case ajm::Format::S16: + return AV_SAMPLE_FMT_S16; + case ajm::Format::S32: + return AV_SAMPLE_FMT_S32; + case ajm::Format::Float: + return AV_SAMPLE_FMT_FLTP; + } + + return AV_SAMPLE_FMT_NONE; +} + +struct FFmpegCodecInstance : ajm::CodecInstance { + std::uint64_t runJob(const ajm::Job &job) override { return 0; } + void reset() override {} +}; + +struct AACInstance { + AACHeaderType headerType; + orbis::uint32_t sampleRate; +}; + +// struct Instance { +// orbis::shared_mutex mtx; +// CodecId codecId; +// ChannelCount maxChannels; +// Format outputFormat; +// At9Instance at9; +// AACInstance aac; +// orbis::kvector inputBuffer; +// orbis::kvector outputBuffer; + +// AVCodecContext *codecCtx; +// SwrContext *resampler; +// orbis::uint32_t processedSamples; +// SidebandGaplessDecode gapless; +// SidebandFormat lastDecode; +// }; + +struct MP3FFmpegCodecInstance : FFmpegCodecInstance { + std::uint64_t runJob(const ajm::Job &job) override { return 0; } + void reset() override {} +}; +struct AACFFmpegCodecInstance : FFmpegCodecInstance { + std::uint64_t runJob(const ajm::Job &job) override { return 0; } + void reset() override {} +}; + +struct MP3FFmpegCodec : ajm::Codec { + orbis::ErrorCode createInstance(orbis::Ref *instance, + std::uint32_t unk0, + std::uint64_t flags) override { + auto mp3 = orbis::Ref{orbis::knew()}; + *instance = mp3; + return {}; + } +}; + +struct AACFFmpegCodec : ajm::Codec { + orbis::ErrorCode createInstance(orbis::Ref *instance, + std::uint32_t unk0, + std::uint64_t flags) override { + auto aac = orbis::Ref{orbis::knew()}; + *instance = aac; + return {}; + } +}; + +void createFFmpegCodecs(AjmDevice *ajm) { + ajm->createCodec(ajm::CodecId::AAC); + ajm->createCodec(ajm::CodecId::MP3); +} diff --git a/rpcsx/audio/FFmpegCodecs.hpp b/rpcsx/audio/FFmpegCodecs.hpp new file mode 100644 index 000000000..5c9c68052 --- /dev/null +++ b/rpcsx/audio/FFmpegCodecs.hpp @@ -0,0 +1,3 @@ +#pragma once + +void createFFmpegCodecs(class AjmDevice *ajm); diff --git a/rpcsx/iodev/ajm.cpp b/rpcsx/iodev/ajm.cpp index af764e76c..6b700b2f6 100644 --- a/rpcsx/iodev/ajm.cpp +++ b/rpcsx/iodev/ajm.cpp @@ -7,224 +7,251 @@ #include "orbis/utils/Logs.hpp" #include #include +#include #include #include -extern "C" { -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +namespace ajm { +enum class Opcode : std::uint8_t { + RunBufferRa = 1, + ControlBufferRa = 2, + Flags = 4, + ReturnAddress = 6, + JobBufferOutputRa = 17, + JobBufferSidebandRa = 18, +}; + +struct InstructionHeader { + orbis::uint32_t id; + orbis::uint32_t len; +}; + +static_assert(sizeof(InstructionHeader) == 0x8); + +struct OpcodeHeader { + orbis::uint32_t opcode; + + [[nodiscard]] Opcode getOpcode() const { + // ORBIS_LOG_ERROR(__FUNCTION__, opcode); + if (auto loType = static_cast(opcode & 0xf); + loType == Opcode::ReturnAddress || loType == Opcode::Flags) { + return loType; + } + + return static_cast(opcode & 0x1f); + } +}; + +struct ReturnAddress { + orbis::uint32_t opcode; + orbis::uint32_t unk; // 0, padding? + orbis::ptr returnAddress; +}; +static_assert(sizeof(ReturnAddress) == 0x10); + +struct BatchJobControlBufferRa { + orbis::uint32_t opcode; + orbis::uint32_t sidebandInputSize; + orbis::ptr pSidebandInput; + orbis::uint32_t flagsHi; + orbis::uint32_t flagsLo; + orbis::uint32_t commandId; + orbis::uint32_t sidebandOutputSize; + orbis::ptr pSidebandOutput; + + std::uint64_t getFlags() { return ((uint64_t)flagsHi << 0x1a) | flagsLo; } +}; +static_assert(sizeof(BatchJobControlBufferRa) == 0x28); + +struct BatchJobInputBufferRa { + orbis::uint32_t opcode; + orbis::uint32_t szInputSize; + orbis::ptr pInput; +}; +static_assert(sizeof(BatchJobInputBufferRa) == 0x10); + +struct BatchJobFlagsRa { + orbis::uint32_t flagsHi; + orbis::uint32_t flagsLo; +}; + +static_assert(sizeof(BatchJobFlagsRa) == 0x8); + +struct BatchJobOutputBufferRa { + orbis::uint32_t opcode; + orbis::uint32_t outputSize; + orbis::ptr pOutput; +}; +static_assert(sizeof(BatchJobOutputBufferRa) == 0x10); + +struct BatchJobSidebandBufferRa { + orbis::uint32_t opcode; + orbis::uint32_t sidebandSize; + orbis::ptr pSideband; +}; +static_assert(sizeof(BatchJobSidebandBufferRa) == 0x10); + +static Job decodeJob(const std::byte *ptr, const std::byte *endPtr) { + Job result{}; + while (ptr < endPtr) { + auto typed = (ajm::OpcodeHeader *)ptr; + switch (typed->getOpcode()) { + case ajm::Opcode::ReturnAddress: { + // ReturnAddress *ra = (ReturnAddress *)jobPtr; + // ORBIS_LOG_ERROR(__FUNCTION__, request, "return address", + // ra->opcode, + // ra->unk, ra->returnAddress); + ptr += sizeof(ajm::ReturnAddress); + break; + } + case ajm::Opcode::ControlBufferRa: { + auto *ctrl = (ajm::BatchJobControlBufferRa *)ptr; + auto *sidebandResult = + reinterpret_cast(ctrl->pSidebandOutput); + *sidebandResult = {}; + result.pSidebandResult = sidebandResult; + result.controlFlags = ctrl->getFlags(); + ptr += sizeof(ajm::BatchJobControlBufferRa); + break; + } + case ajm::Opcode::RunBufferRa: { + auto *job = (ajm::BatchJobInputBufferRa *)ptr; + // ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobInputBufferRa", + // job->opcode, job->szInputSize, job->pInput); + + // rx::hexdump({(std::byte*) job->pInput, job->szInputSize}); + result.inputBuffers.push_back({job->pInput, job->szInputSize}); + ptr += sizeof(ajm::BatchJobInputBufferRa); + break; + } + case ajm::Opcode::Flags: { + auto *job = (ajm::BatchJobFlagsRa *)ptr; + // ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobFlagsRa", + // job->flagsHi, job->flagsLo); + result.flags = ((orbis::uint64_t)job->flagsHi << 0x1a) | job->flagsLo; + ptr += sizeof(ajm::BatchJobFlagsRa); + break; + } + case ajm::Opcode::JobBufferOutputRa: { + auto *job = (ajm::BatchJobOutputBufferRa *)ptr; + // ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobOutputBufferRa", + // job->opcode, job->outputSize, job->pOutput); + result.outputBuffers.push_back({job->pOutput, job->outputSize}); + result.totalOutputSize += job->outputSize; + ptr += sizeof(ajm::BatchJobOutputBufferRa); + break; + } + case ajm::Opcode::JobBufferSidebandRa: { + auto *job = (ajm::BatchJobSidebandBufferRa *)ptr; + // ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobSidebandBufferRa", + // job->opcode, job->sidebandSize, job->pSideband); + result.pSideband = job->pSideband; + result.sidebandSize = job->sidebandSize; + ptr += sizeof(ajm::BatchJobSidebandBufferRa); + break; + } + default: + ORBIS_LOG_ERROR(__FUNCTION__, "unexpected job opcode", + typed->getOpcode()); + ptr = endPtr; + break; + } + } + + return result; } +} // namespace ajm struct AjmFile : orbis::File {}; -enum { - AJM_RESULT_INVALID_DATA = 0x2, - AJM_RESULT_INVALID_PARAMETER = 0x4, - AJM_RESULT_PARTIAL_INPUT = 0x8, - AJM_RESULT_NOT_ENOUGH_ROOM = 0x10, - AJM_RESULT_STREAM_CHANGE = 0x20, - AJM_RESULT_TOO_MANY_CHANNELS = 0x40, - AJM_RESULT_UNSUPPORTED_FLAG = 0x80, - AJM_RESULT_SIDEBAND_TRUNCATED = 0x100, - AJM_RESULT_PRIORITY_PASSED = 0x200, - AJM_RESULT_FATAL = 0x80000000, - AJM_RESULT_CODEC_ERROR = 0x40000000, +enum AjmIoctlRequest { + AJM_IOCTL_CONTEXT_UNREGISTER = 0xc0288900, + AJM_IOCTL_MODULE_REGISTER = 0xc0288903, + AJM_IOCTL_MODULE_UNREGISTER = 0xc0288904, + AJM_IOCTL_INSTANCE_CREATE = 0xc0288905, + AJM_IOCTL_INSTANCE_DESTROY = 0xc0288906, + AJM_IOCTL_INSTANCE_EXTEND = 0xc028890a, + AJM_IOCTL_INSTANCE_SWITCH = 0xc028890b, + AJM_IOCTL_BATCH_RUN = 0xc0288907, + AJM_IOCTL_BATCH_WAIT = 0xc0288908, }; -struct AjmDevice : IoDevice { - orbis::shared_mutex mtx; - orbis::uint32_t batchId = 1; // temp - - orbis::uint32_t instanceIds[AJM_CODEC_COUNT]{}; - orbis::uint32_t unimplementedInstanceId = 0; - orbis::kmap instanceMap; - orbis::ErrorCode open(orbis::Ref *file, const char *path, - std::uint32_t flags, std::uint32_t mode, - orbis::Thread *thread) override; -}; - -AVSampleFormat ajmToAvFormat(AJMFormat ajmFormat) { - switch (ajmFormat) { - case AJM_FORMAT_S16: - return AV_SAMPLE_FMT_S16; - case AJM_FORMAT_S32: - return AV_SAMPLE_FMT_S32; - case AJM_FORMAT_FLOAT: - return AV_SAMPLE_FMT_FLTP; - default: - return AV_SAMPLE_FMT_NONE; - } -} - -void reset(Instance *instance) { - instance->gapless.skipSamples = 0; - instance->gapless.totalSamples = 0; - instance->gapless.totalSkippedSamples = 0; - instance->processedSamples = 0; -} - -void resetAt9(Instance *instance) { - if (instance->at9.configData) { - Atrac9ReleaseHandle(instance->at9.handle); - instance->at9.estimatedSizeUsed = 0; - instance->at9.superFrameSize = 0; - instance->at9.framesInSuperframe = 0; - instance->at9.frameSamples = 0; - instance->at9.sampleRate = 0; - instance->at9.superFrameDataIdx = 0; - instance->at9.inputChannels = 0; - instance->at9.superFrameDataLeft = 0; - instance->at9.handle = Atrac9GetHandle(); - int err = Atrac9InitDecoder(instance->at9.handle, - (uint8_t *)&instance->at9.configData); - if (err < 0) { - ORBIS_LOG_FATAL("AT9 Init Decoder error", err); - std::abort(); - } - Atrac9CodecInfo pCodecInfo; - Atrac9GetCodecInfo(instance->at9.handle, &pCodecInfo); - - instance->at9.frameSamples = pCodecInfo.frameSamples; - instance->at9.inputChannels = pCodecInfo.channels; - instance->at9.framesInSuperframe = pCodecInfo.framesInSuperframe; - instance->at9.superFrameDataIdx = 0; - instance->at9.superFrameSize = pCodecInfo.superframeSize; - instance->at9.superFrameDataLeft = pCodecInfo.superframeSize; - instance->at9.sampleRate = pCodecInfo.samplingRate; - } -} - static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, void *argp, orbis::Thread *thread) { auto device = static_cast(file->device.get()); - // 0xc0288900 - finalize - // 0xc0288903 - module register - // 0xc0288904 - module unregister - // 0xc0288905 - instance create - // 0xc0288906 - instance destroy - // 0xc028890a - instance extend - // 0xc028890b - intasnce switch - // 0xc0288907 - start batch buffer - // 0xc0288908 - wait batch buffer - // 0xc0288900 - unregister context - if (request == 0xc0288906) { - struct InstanceDestroyArgs { - orbis::uint32_t result; - orbis::uint32_t unk0; - orbis::uint32_t instanceId; - }; - auto args = reinterpret_cast(argp); - if (device->instanceMap.erase(args->instanceId) == 0) { - return orbis::ErrorCode::INVAL; - } + switch (AjmIoctlRequest(request)) { + case AJM_IOCTL_CONTEXT_UNREGISTER: + case AJM_IOCTL_MODULE_REGISTER: + case AJM_IOCTL_MODULE_UNREGISTER: { + struct Args { + orbis::uint32_t result; + }; + + auto args = reinterpret_cast(argp); + ORBIS_LOG_ERROR(__FUNCTION__, request, args); args->result = 0; return {}; } - if (request == 0xc0288903 || request == 0xc0288904 || request == 0xc0288900) { - auto arg = reinterpret_cast(argp)[2]; - ORBIS_LOG_ERROR(__FUNCTION__, request, arg); - *reinterpret_cast(argp) = 0; - return {}; - } - - if (request == 0xc0288905) { + case AJM_IOCTL_INSTANCE_CREATE: { struct InstanceCreateArgs { orbis::uint32_t result; orbis::uint32_t unk0; orbis::uint64_t flags; orbis::uint32_t codec; - orbis::uint32_t instanceId; + ajm::PackedInstanceId instanceId; }; auto args = reinterpret_cast(argp); - auto codecId = AJMCodecs(args->codec); - auto codecOffset = codecId << 0xe; - if (codecId < AJM_CODEC_COUNT) { - args->result = 0; - args->instanceId = codecOffset + device->instanceIds[codecId]++; - - auto [it, inserted] = device->instanceMap.try_emplace(args->instanceId); - - assert(inserted); - auto &instance = it->second; - instance.codec = codecId; - instance.maxChannels = - AJMChannels(((args->flags & ~7) & (0xFF & ~0b11)) >> 3); - instance.outputFormat = AJMFormat((args->flags & ~7) & 0b11); - if (codecId == AJM_CODEC_At9) { - instance.at9.handle = Atrac9GetHandle(); - if (instance.outputFormat == AJM_FORMAT_S16) { - instance.at9.outputFormat = kAtrac9FormatS16; - } else if (instance.outputFormat == AJM_FORMAT_S32) { - instance.at9.outputFormat = kAtrac9FormatS32; - } else if (instance.outputFormat == AJM_FORMAT_FLOAT) { - instance.at9.outputFormat = kAtrac9FormatF32; - } else { - ORBIS_LOG_FATAL("Unexpected AT9 output format", - (std::uint32_t)instance.outputFormat); - return orbis::ErrorCode::INVAL; - } - } - - if (codecId == AJM_CODEC_AAC || codecId == AJM_CODEC_MP3) { - const AVCodec *codec = avcodec_find_decoder( - codecId == AJM_CODEC_AAC ? AV_CODEC_ID_AAC : AV_CODEC_ID_MP3); - if (!codec) { - ORBIS_LOG_FATAL("Codec not found", (orbis::uint32_t)codecId); - std::abort(); - } - AVCodecContext *codecCtx = avcodec_alloc_context3(codec); - if (!codecCtx) { - ORBIS_LOG_FATAL("Failed to allocate codec context"); - std::abort(); - } - - if (int err = avcodec_open2(codecCtx, codec, nullptr); err < 0) { - ORBIS_LOG_FATAL("Could not open codec", err); - std::abort(); - } - - instance.codecCtx = codecCtx; - } - - ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0, - args->flags, args->codec, args->instanceId, - (std::uint32_t)instance.outputFormat); - - } else { - args->instanceId = codecOffset + device->unimplementedInstanceId++; - ORBIS_LOG_ERROR(__FUNCTION__, request, "unimplemented codec", - args->result, args->unk0, args->flags, args->codec, - args->instanceId); - } + auto codecId = ajm::CodecId(args->codec); + orbis::Ref instance; + ORBIS_RET_ON_ERROR( + device->createInstance(&instance, codecId, args->unk0, args->flags)); + auto instanceId = device->addCodecInstance(codecId, instance); + args->result = 0; + args->instanceId = ajm::PackedInstanceId::create(codecId, instanceId); return {}; } - if (request == 0xc0288907) { - struct StartBatchBufferArgs { + case AJM_IOCTL_INSTANCE_DESTROY: { + struct InstanceDestroyArgs { + orbis::uint32_t result; + orbis::uint32_t unk0; + ajm::PackedInstanceId instanceId; + }; + auto args = reinterpret_cast(argp); + auto packedInstanceId = args->instanceId; + ORBIS_RET_ON_ERROR(device->removeInstance( + packedInstanceId.getCodecId(), packedInstanceId.getInstanceId())); + args->result = 0; + return {}; + } + + case AJM_IOCTL_INSTANCE_EXTEND: + std::println(stderr, "ajm instance extend"); + std::abort(); + return {}; + + case AJM_IOCTL_INSTANCE_SWITCH: + std::println(stderr, "ajm instance switch"); + std::abort(); + return {}; + + case AJM_IOCTL_BATCH_RUN: { + struct BatchRunBufferArgs { orbis::uint32_t result; orbis::uint32_t unk0; std::byte *pBatch; orbis::uint32_t batchSize; orbis::uint32_t priority; orbis::uint64_t batchError; - orbis::uint32_t batchId; + ajm::BatchId batchId; }; - auto args = reinterpret_cast(argp); - args->result = 0; - args->batchId = device->batchId++; + + auto args = reinterpret_cast(argp); // ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0, // args->pBatch, args->batchSize, args->priority, // args->batchError, args->batchId); @@ -233,488 +260,64 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, auto ptr = args->pBatch; auto endPtr = args->pBatch + args->batchSize; + std::map, std::vector> runJobMap; + while (ptr < endPtr) { - auto header = (InstructionHeader *)ptr; - auto instanceId = (header->id >> 6) & 0xfffff; - auto jobPtr = ptr + sizeof(InstructionHeader); + auto header = (ajm::InstructionHeader *)ptr; + auto instanceId = ajm::PackedInstanceId{(header->id >> 6) & 0xfffff}; + auto jobPtr = ptr + sizeof(ajm::InstructionHeader); auto endJobPtr = ptr + header->len; // TODO: handle unimplemented codecs, so auto create instance for now - auto &instance = device->instanceMap[instanceId]; + auto runJob = ajm::decodeJob(jobPtr, endJobPtr); + ptr = endJobPtr; - instance.inputBuffer.clear(); - RunJob runJob{}; - while (jobPtr < endJobPtr) { - auto typed = (OpcodeHeader *)jobPtr; - switch (typed->getOpcode()) { - case Opcode::ReturnAddress: { - // ReturnAddress *ra = (ReturnAddress *)jobPtr; - // ORBIS_LOG_ERROR(__FUNCTION__, request, "return address", - // ra->opcode, - // ra->unk, ra->returnAddress); - jobPtr += sizeof(ReturnAddress); - break; - } - case Opcode::ControlBufferRa: { - runJob.control = true; - auto *ctrl = (BatchJobControlBufferRa *)jobPtr; - auto *result = - reinterpret_cast(ctrl->pSidebandOutput); - *result = {}; + auto instance = device->getInstance(instanceId.getCodecId(), + instanceId.getInstanceId()); - ORBIS_LOG_ERROR(__FUNCTION__, request, "control buffer", ctrl->opcode, - ctrl->commandId, ctrl->flagsHi, ctrl->flagsLo, - ctrl->sidebandInputSize, ctrl->sidebandOutputSize); - if (ctrl->getFlags() & CONTROL_RESET) { - reset(&instance); - if (instance.codec == AJM_CODEC_At9) { - resetAt9(&instance); - } - } - - if (ctrl->getFlags() & CONTROL_INITIALIZE) { - if (instance.codec == AJM_CODEC_At9) { - struct InitalizeBuffer { - orbis::uint32_t configData; - orbis::int32_t unk0[2]; - }; - auto *initializeBuffer = (InitalizeBuffer *)ctrl->pSidebandInput; - instance.at9.configData = initializeBuffer->configData; - reset(&instance); - resetAt9(&instance); - - orbis::uint32_t maxChannels = - instance.maxChannels == AJM_CHANNEL_DEFAULT - ? 2 - : instance.maxChannels; - orbis::uint32_t outputChannels = - instance.at9.inputChannels > maxChannels - ? maxChannels - : instance.at9.inputChannels; - // TODO: check max channels - ORBIS_LOG_TODO("CONTROL_INITIALIZE", instance.at9.inputChannels, - instance.at9.sampleRate, instance.at9.frameSamples, - instance.at9.superFrameSize, maxChannels, - outputChannels, initializeBuffer->configData, - (orbis::uint32_t)instance.outputFormat); - } else if (instance.codec == AJM_CODEC_AAC) { - struct InitializeBuffer { - orbis::uint32_t headerIndex; - orbis::uint32_t sampleRateIndex; - }; - auto *initializeBuffer = (InitializeBuffer *)ctrl->pSidebandInput; - instance.aac.headerType = - AACHeaderType(initializeBuffer->headerIndex); - instance.aac.sampleRate = - AACFreq[initializeBuffer->sampleRateIndex]; - } - } - if (ctrl->getFlags() & SIDEBAND_GAPLESS_DECODE) { - struct InitializeBuffer { - orbis::uint32_t totalSamples; - orbis::uint16_t skipSamples; - orbis::uint16_t totalSkippedSamples; - }; - - auto *initializeBuffer = (InitializeBuffer *)ctrl->pSidebandInput; - if (initializeBuffer->totalSamples > 0) { - instance.gapless.totalSamples = initializeBuffer->totalSamples; - } - if (initializeBuffer->skipSamples > 0) { - instance.gapless.skipSamples = initializeBuffer->skipSamples; - } - ORBIS_LOG_TODO("SIDEBAND_GAPLESS_DECODE", - instance.gapless.skipSamples, - instance.gapless.totalSamples); - } - jobPtr += sizeof(BatchJobControlBufferRa); - break; - } - case Opcode::RunBufferRa: { - auto *job = (BatchJobInputBufferRa *)jobPtr; - // ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobInputBufferRa", - // job->opcode, job->szInputSize, job->pInput); - - auto offset = instance.inputBuffer.size(); - instance.inputBuffer.resize(offset + job->szInputSize); - - std::memcpy(instance.inputBuffer.data() + offset, job->pInput, - job->szInputSize); - // rx::hexdump({(std::byte*) job->pInput, job->szInputSize}); - jobPtr += sizeof(BatchJobInputBufferRa); - break; - } - case Opcode::Flags: { - auto *job = (BatchJobFlagsRa *)jobPtr; - // ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobFlagsRa", - // job->flagsHi, job->flagsLo); - runJob.flags = ((orbis::uint64_t)job->flagsHi << 0x1a) | job->flagsLo; - jobPtr += sizeof(BatchJobFlagsRa); - break; - } - case Opcode::JobBufferOutputRa: { - auto *job = (BatchJobOutputBufferRa *)jobPtr; - // ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobOutputBufferRa", - // job->opcode, job->outputSize, job->pOutput); - runJob.outputBuffers.push_back({job->pOutput, job->outputSize}); - runJob.totalOutputSize += job->outputSize; - jobPtr += sizeof(BatchJobOutputBufferRa); - break; - } - case Opcode::JobBufferSidebandRa: { - auto *job = (BatchJobSidebandBufferRa *)jobPtr; - // ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobSidebandBufferRa", - // job->opcode, job->sidebandSize, job->pSideband); - runJob.pSideband = job->pSideband; - runJob.sidebandSize = job->sidebandSize; - jobPtr += sizeof(BatchJobSidebandBufferRa); - break; - } - default: - jobPtr = endJobPtr; - break; - } + if (instance == nullptr) { + return orbis::ErrorCode::BADF; } - ptr = jobPtr; - if (!runJob.control && instanceId >= 0xC000) { - auto *result = reinterpret_cast(runJob.pSideband); - result->result = 0; - result->codecResult = 0; - if (runJob.flags & SIDEBAND_STREAM) { - auto *stream = - reinterpret_cast(runJob.pSideband + 8); - stream->inputSize = instance.inputBuffer.size(); - stream->outputSize = runJob.totalOutputSize; - } - } else if (!runJob.control) { - // orbis::uint32_t maxChannels = - // instance.maxChannels == AJM_CHANNEL_DEFAULT ? 2 - // : - // instance.maxChannels; - auto *result = reinterpret_cast(runJob.pSideband); - *result = {}; - - orbis::uint32_t totalDecodedBytes = 0; - orbis::uint32_t outputWritten = 0; - orbis::uint32_t framesProcessed = 0; - orbis::uint32_t samplesCount = 0; - if (!instance.inputBuffer.empty() && runJob.totalOutputSize != 0) { - instance.inputBuffer.reserve(instance.inputBuffer.size() + - AV_INPUT_BUFFER_PADDING_SIZE); - - AVPacket *pkt = av_packet_alloc(); - rx::atScopeExit _free_pkt([&] { av_packet_free(&pkt); }); - - AVFrame *frame = av_frame_alloc(); - rx::atScopeExit _free_frame([&] { av_frame_free(&frame); }); - - do { - if (instance.codec == AJM_CODEC_At9 && - instance.at9.frameSamples == 0) { - break; - } - if (totalDecodedBytes >= instance.inputBuffer.size()) { - break; - } - - framesProcessed++; - - std::uint32_t inputFrameSize = 0; - std::uint32_t outputBufferSize = 0; - - if (instance.codec == AJM_CODEC_At9) { - inputFrameSize = 4; - outputBufferSize = av_samples_get_buffer_size( - nullptr, instance.at9.inputChannels, - instance.at9.frameSamples, - ajmToAvFormat(instance.outputFormat), 0); - } else if (instance.codec == AJM_CODEC_MP3) { - if (instance.inputBuffer.size() - totalDecodedBytes < 4) { - result->result = AJM_RESULT_INVALID_DATA; - break; - } - - inputFrameSize = get_mp3_data_size( - (orbis::uint8_t *)(instance.inputBuffer.data() + - totalDecodedBytes)); - if (inputFrameSize == 0) { - result->result = AJM_RESULT_INVALID_DATA; - break; - } - } else if (instance.codec == AJM_CODEC_AAC) { - inputFrameSize = instance.inputBuffer.size() - totalDecodedBytes; - } - - if (inputFrameSize > - instance.inputBuffer.size() - totalDecodedBytes) { - result->result |= AJM_RESULT_PARTIAL_INPUT; - break; - } - - if (outputBufferSize > runJob.totalOutputSize - outputWritten) { - result->result |= AJM_RESULT_NOT_ENOUGH_ROOM; - break; - } - - pkt->data = - (std::uint8_t *)instance.inputBuffer.data() + totalDecodedBytes; - pkt->size = inputFrameSize; - - if (instance.codec == AJM_CODEC_At9) { - orbis::int32_t bytesUsed = 0; - instance.outputBuffer.resize(outputBufferSize); - int err = - Atrac9Decode(instance.at9.handle, - instance.inputBuffer.data() + totalDecodedBytes, - instance.outputBuffer.data(), - instance.at9.outputFormat, &bytesUsed); - if (err != ERR_SUCCESS) { - rx::hexdump( - std::span(instance.inputBuffer).subspan(totalDecodedBytes)); - ORBIS_LOG_FATAL("Could not decode frame", err, - instance.at9.estimatedSizeUsed, - instance.at9.superFrameSize, - instance.at9.frameSamples, instance.at9.handle, - totalDecodedBytes, outputWritten); - std::abort(); - } - - instance.at9.estimatedSizeUsed = - static_cast(bytesUsed); - instance.at9.superFrameDataLeft -= bytesUsed; - instance.at9.superFrameDataIdx++; - if (instance.at9.superFrameDataIdx == - instance.at9.framesInSuperframe) { - instance.at9.estimatedSizeUsed += - instance.at9.superFrameDataLeft; - instance.at9.superFrameDataIdx = 0; - instance.at9.superFrameDataLeft = instance.at9.superFrameSize; - } - samplesCount = instance.at9.frameSamples; - inputFrameSize = instance.at9.estimatedSizeUsed; - instance.lastDecode.channels = - AJMChannels(instance.at9.inputChannels); - instance.lastDecode.sampleRate = instance.at9.sampleRate; - // ORBIS_LOG_TODO("at9 decode", instance.at9.estimatedSizeUsed, - // instance.at9.superFrameDataLeft, - // instance.at9.superFrameDataIdx, - // instance.at9.framesInSuperframe); - } else if (instance.codec == AJM_CODEC_MP3) { - int ret = avcodec_send_packet(instance.codecCtx, pkt); - if (ret < 0) { - ORBIS_LOG_FATAL("Error sending packet for decoding", ret); - std::abort(); - } - ret = avcodec_receive_frame(instance.codecCtx, frame); - if (ret < 0) { - ORBIS_LOG_FATAL("Error during decoding"); - std::abort(); - } - outputBufferSize = av_samples_get_buffer_size( - nullptr, frame->ch_layout.nb_channels, frame->nb_samples, - ajmToAvFormat(instance.outputFormat), 0); - - samplesCount = frame->nb_samples; - instance.lastDecode.channels = - AJMChannels(frame->ch_layout.nb_channels); - instance.lastDecode.sampleRate = frame->sample_rate; - } else if (instance.codec == AJM_CODEC_AAC) { - // HACK: to avoid writing a bunch of useless calls - // we simply call this method directly (but it can be very - // unstable) - int gotFrame; - int len = - ffcodec(instance.codecCtx->codec) - ->cb.decode(instance.codecCtx, frame, &gotFrame, pkt); - if (len < 0) { - ORBIS_LOG_FATAL("Error during decoding"); - std::abort(); - } - outputBufferSize = av_samples_get_buffer_size( - nullptr, frame->ch_layout.nb_channels, frame->nb_samples, - ajmToAvFormat(instance.outputFormat), 0); - samplesCount = frame->nb_samples; - inputFrameSize = len; - instance.lastDecode.channels = - AJMChannels(frame->ch_layout.nb_channels); - instance.lastDecode.sampleRate = frame->sample_rate; - } - - if (inputFrameSize > - instance.inputBuffer.size() - totalDecodedBytes) { - result->result |= AJM_RESULT_PARTIAL_INPUT; - break; - } - - if (outputBufferSize > runJob.totalOutputSize - outputWritten) { - result->result |= AJM_RESULT_NOT_ENOUGH_ROOM; - break; - } - - totalDecodedBytes += inputFrameSize; - - if (instance.gapless.skipSamples > 0 || - instance.gapless.totalSamples > 0) { - if (instance.gapless.totalSkippedSamples < - instance.gapless.skipSamples || - instance.processedSamples > instance.gapless.totalSamples) { - instance.gapless.totalSkippedSamples += samplesCount; - continue; - } - } - // at least three codecs outputs in float - // and mp3 support sample rate resample (TODO), so made resampling - // with swr - if (instance.codec != AJM_CODEC_At9) { - instance.outputBuffer.resize(outputBufferSize); - - if (instance.resampler == nullptr) { - instance.resampler = swr_alloc(); - auto resampler = instance.resampler; - - AVChannelLayout chLayout; - av_channel_layout_default(&chLayout, - frame->ch_layout.nb_channels); - av_opt_set_chlayout(resampler, "in_chlayout", &chLayout, 0); - av_opt_set_chlayout(resampler, "out_chlayout", &chLayout, 0); - av_opt_set_int(resampler, "in_sample_rate", frame->sample_rate, - 0); - av_opt_set_int(resampler, "out_sample_rate", frame->sample_rate, - 0); - av_opt_set_sample_fmt(resampler, "in_sample_fmt", - ajmToAvFormat(AJM_FORMAT_FLOAT), 0); - av_opt_set_sample_fmt(resampler, "out_sample_fmt", - ajmToAvFormat(instance.outputFormat), 0); - if (swr_init(resampler) < 0) { - ORBIS_LOG_FATAL( - "Failed to initialize the resampling context"); - std::abort(); - } - } - - auto *outputBuffer = reinterpret_cast( - instance.outputBuffer.data()); - int nb_samples = swr_convert( - instance.resampler, &outputBuffer, frame->nb_samples, - frame->extended_data, frame->nb_samples); - if (nb_samples != frame->nb_samples) { - ORBIS_LOG_FATAL("Error while converting"); - std::abort(); - } - } - - std::uint32_t bufferOutputWritten = 0; - for (std::size_t bufferOffset = 0; - auto buffer : runJob.outputBuffers) { - if (bufferOffset <= outputWritten && - bufferOffset + buffer.size > outputWritten) { - auto byteOffset = outputWritten - bufferOffset; - auto size = std::min(buffer.size - byteOffset, - instance.outputBuffer.size() - - bufferOutputWritten); - ORBIS_RET_ON_ERROR(orbis::uwrite( - buffer.pOutput + byteOffset, - instance.outputBuffer.data() + bufferOutputWritten, size)); - - bufferOutputWritten += size; - outputWritten += size; - - if (bufferOutputWritten >= instance.outputBuffer.size()) { - break; - } - } - - bufferOffset += buffer.size; - } - - instance.processedSamples += samplesCount; - } while ((runJob.flags & RUN_MULTIPLE_FRAMES) != 0); - } - - orbis::int64_t currentSize = sizeof(AJMSidebandResult); - - if (runJob.flags & SIDEBAND_STREAM) { - // ORBIS_LOG_TODO("SIDEBAND_STREAM", currentSize, inputReaded, - // outputWritten, instance.processedSamples); - auto *stream = reinterpret_cast( - runJob.pSideband + currentSize); - stream->inputSize = totalDecodedBytes; - stream->outputSize = outputWritten; - stream->decodedSamples = instance.processedSamples; - currentSize += sizeof(AJMSidebandStream); - } - - if (runJob.flags & SIDEBAND_FORMAT) { - // ORBIS_LOG_TODO("SIDEBAND_FORMAT", currentSize); - auto *format = reinterpret_cast( - runJob.pSideband + currentSize); - format->channels = AJMChannels(instance.lastDecode.channels); - format->sampleRate = instance.lastDecode.sampleRate; - format->sampleFormat = instance.outputFormat; - // TODO: channel mask and bitrate - currentSize += sizeof(AJMSidebandFormat); - } - - if (runJob.flags & SIDEBAND_GAPLESS_DECODE) { - // ORBIS_LOG_TODO("SIDEBAND_GAPLESS_DECODE", currentSize); - auto *gapless = reinterpret_cast( - runJob.pSideband + currentSize); - gapless->skipSamples = instance.gapless.skipSamples; - gapless->totalSamples = instance.gapless.totalSamples; - gapless->totalSkippedSamples = instance.gapless.totalSkippedSamples; - currentSize += sizeof(AJMSidebandGaplessDecode); - } - - if (runJob.flags & RUN_GET_CODEC_INFO) { - // ORBIS_LOG_TODO("RUN_GET_CODEC_INFO"); - if (instance.codec == AJM_CODEC_At9) { - auto *info = reinterpret_cast( - runJob.pSideband + currentSize); - info->superFrameSize = instance.at9.superFrameSize; - info->framesInSuperFrame = instance.at9.framesInSuperframe; - info->frameSamples = instance.at9.frameSamples; - currentSize += sizeof(AJMAt9CodecInfoSideband); - } else if (instance.codec == AJM_CODEC_MP3) { - // TODO - auto *info = reinterpret_cast( - runJob.pSideband + currentSize); - currentSize += sizeof(AJMMP3CodecInfoSideband); - } else if (instance.codec == AJM_CODEC_AAC) { - // TODO - auto *info = reinterpret_cast( - runJob.pSideband + currentSize); - currentSize += sizeof(AJMAACCodecInfoSideband); - } - } - - if (runJob.flags & RUN_MULTIPLE_FRAMES) { - // ORBIS_LOG_TODO("RUN_MULTIPLE_FRAMES", framesProcessed); - auto *multipleFrames = reinterpret_cast( - runJob.pSideband + currentSize); - multipleFrames->framesProcessed = framesProcessed; - currentSize += sizeof(AJMSidebandMultipleFrames); - } - } + auto [it, inserted] = runJobMap.try_emplace(std::move(instance)); + it->second.push_back(std::move(runJob)); } + auto batch = orbis::knew(); + + for (auto &[instance, runJobs] : runJobMap) { + instance->runBatch(batch, std::move(runJobs)); + } + + args->result = 0; + args->batchId = device->addBatch(batch); return {}; } - - if (request == 0xc0288908) { + case AJM_IOCTL_BATCH_WAIT: { struct Args { - orbis::uint32_t unk0; + orbis::uint32_t result; orbis::uint32_t unk1; - orbis::uint32_t batchId; + ajm::BatchId batchId; orbis::uint32_t timeout; orbis::uint64_t batchError; }; auto args = reinterpret_cast(argp); - args->unk0 = 0; + auto batch = device->getBatch(args->batchId); + if (batch == nullptr) { + return orbis::ErrorCode::BADF; + } + // ORBIS_LOG_ERROR(__FUNCTION__, request, args->unk0, args->unk1, // args->batchId, args->timeout, args->batchError); // thread->where(); + + std::uint64_t batchError = 0; + ORBIS_RET_ON_ERROR(batch->wait(args->timeout, &batchError)); + args->result = 0; + args->batchError = batchError; + return {}; } + } ORBIS_LOG_FATAL("Unhandled AJM ioctl", request); thread->where(); diff --git a/rpcsx/iodev/ajm.hpp b/rpcsx/iodev/ajm.hpp index 53857dcc8..2c9005724 100644 --- a/rpcsx/iodev/ajm.hpp +++ b/rpcsx/iodev/ajm.hpp @@ -1,368 +1,401 @@ #pragma once -#include "libatrac9/libatrac9.h" +#include "io-device.hpp" #include "orbis-config.hpp" #include "orbis/KernelAllocator.hpp" -#include "orbis/utils/SharedMutex.hpp" -// #include "orbis/utils/Logs.hpp" +#include "orbis/error.hpp" +#include "orbis/utils/IdMap.hpp" +#include "orbis/utils/Rc.hpp" +#include "orbis/utils/SharedAtomic.hpp" +#include "rx/refl.hpp" +#include +#include +#include #include -extern "C" { -#include -#include -#include -} +#include +#include +#include +#include +#include +#include +#include -enum class Opcode : std::uint8_t { - RunBufferRa = 1, - ControlBufferRa = 2, - Flags = 4, - ReturnAddress = 6, - JobBufferOutputRa = 17, - JobBufferSidebandRa = 18, +namespace ajm { +enum ControlFlags { + kControlInitialize = 0x4000, + kControlReset = 0x2000, }; -struct InstructionHeader { - orbis::uint32_t id; - orbis::uint32_t len; +enum RunFlags { + kRunMultipleFrames = 0x1000, + kRunGetCodecInfo = 0x800, }; -static_assert(sizeof(InstructionHeader) == 0x8); - -struct OpcodeHeader { - orbis::uint32_t opcode; - - Opcode getOpcode() const { - // ORBIS_LOG_ERROR(__FUNCTION__, opcode); - if (auto loType = static_cast(opcode & 0xf); - loType == Opcode::ReturnAddress || loType == Opcode::Flags) { - return loType; - } - - return static_cast(opcode & 0x1f); - } +enum SidebandFlags { + kSidebandStream = 0x800000000000, + kSidebandFormat = 0x400000000000, + kSidebandGaplessDecode = 0x200000000000, }; -struct ReturnAddress { - orbis::uint32_t opcode; - orbis::uint32_t unk; // 0, padding? - orbis::ptr returnAddress; -}; -static_assert(sizeof(ReturnAddress) == 0x10); - -struct BatchJobControlBufferRa { - orbis::uint32_t opcode; - orbis::uint32_t sidebandInputSize; - orbis::ptr pSidebandInput; - orbis::uint32_t flagsHi; - orbis::uint32_t flagsLo; - orbis::uint32_t commandId; - orbis::uint32_t sidebandOutputSize; - orbis::ptr pSidebandOutput; - - std::uint64_t getFlags() { return ((uint64_t)flagsHi << 0x1a) | flagsLo; } -}; -static_assert(sizeof(BatchJobControlBufferRa) == 0x28); - -struct BatchJobInputBufferRa { - orbis::uint32_t opcode; - orbis::uint32_t szInputSize; - orbis::ptr pInput; -}; -static_assert(sizeof(BatchJobInputBufferRa) == 0x10); - -struct BatchJobFlagsRa { - orbis::uint32_t flagsHi; - orbis::uint32_t flagsLo; +enum { + kResultInvalidData = 0x2, + kResultInvalidParameter = 0x4, + kResultPartialInput = 0x8, + kResultNotEnoughRoom = 0x10, + kResultStreamChange = 0x20, + kResultTooManyChannels = 0x40, + kResultUnsupportedFlag = 0x80, + kResultSidebandTruncated = 0x100, + kResultPriorityPassed = 0x200, + kResultCodecError = 0x40000000, + kResultFatal = 0x80000000, }; -static_assert(sizeof(BatchJobFlagsRa) == 0x8); - -struct BatchJobOutputBufferRa { - orbis::uint32_t opcode; - orbis::uint32_t outputSize; - orbis::ptr pOutput; -}; -static_assert(sizeof(BatchJobOutputBufferRa) == 0x10); - -struct BatchJobSidebandBufferRa { - orbis::uint32_t opcode; - orbis::uint32_t sidebandSize; - orbis::ptr pSideband; -}; -static_assert(sizeof(BatchJobSidebandBufferRa) == 0x10); - - -struct AjmOutputBuffer { - orbis::ptr pOutput; - orbis::size_t size; -}; -struct RunJob { - std::uint64_t flags; - std::uint32_t sidebandSize; - std::uint32_t totalOutputSize; - std::vector outputBuffers; - orbis::ptr pSideband; - bool control; +enum class CodecId : std::uint32_t { + MP3 = 0, + At9 = 1, + AAC = 2, }; -// Thanks to mystical SirNickity with 1 post -// https://hydrogenaud.io/index.php?topic=85125.msg747716#msg747716 - -inline constexpr uint8_t mpeg_versions[4] = {25, 0, 2, 1}; - -// Layers - use [layer] -inline constexpr uint8_t mpeg_layers[4] = {0, 3, 2, 1}; - -// Bitrates - use [version][layer][bitrate] -inline constexpr uint16_t mpeg_bitrates[4][4][16] = { - { - // Version 2.5 - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved - {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, - 0}, // Layer 3 - {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, - 0}, // Layer 2 - {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, - 0} // Layer 1 - }, - { - // Reserved - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Invalid - }, - { - // Version 2 - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved - {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, - 0}, // Layer 3 - {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, - 0}, // Layer 2 - {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, - 0} // Layer 1 - }, - { - // Version 1 - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved - {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, - 0}, // Layer 3 - {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, - 0}, // Layer 2 - {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, - 0}, // Layer 1 - }}; - -// Sample rates - use [version][srate] -inline constexpr uint16_t mpeg_srates[4][4] = { - {11025, 12000, 8000, 0}, // MPEG 2.5 - {0, 0, 0, 0}, // Reserved - {22050, 24000, 16000, 0}, // MPEG 2 - {44100, 48000, 32000, 0} // MPEG 1 +enum class ChannelCount : orbis::uint32_t { + Default, + _1, + _2, + _3, + _4, + _5, + _6, + _8, }; -// Samples per frame - use [version][layer] -inline constexpr uint16_t mpeg_frame_samples[4][4] = { - // Rsvd 3 2 1 < Layer v Version - {0, 576, 1152, 384}, // 2.5 - {0, 0, 0, 0}, // Reserved - {0, 576, 1152, 384}, // 2 - {0, 1152, 1152, 384} // 1 +enum class Format : orbis::uint32_t { + S16 = 0, // default + S32 = 1, + Float = 2 }; -// Slot size (MPEG unit of measurement) - use [layer] -inline constexpr uint8_t mpeg_slot_size[4] = {0, 1, 1, 4}; // Rsvd, 3, 2, 1 +enum class BatchId : std::uint32_t {}; +enum class InstanceId : std::uint32_t {}; -constexpr uint32_t get_mp3_data_size(const uint8_t *data) { - // Quick validity check - if (((data[0] & 0xFF) != 0xFF) || ((data[1] & 0xE0) != 0xE0) // 3 sync bits - || ((data[1] & 0x18) == 0x08) // Version rsvd - || ((data[1] & 0x06) == 0x00) // Layer rsvd - || ((data[2] & 0xF0) == 0xF0) // Bitrate rsvd - ) { - return 0; +struct PackedInstanceId { + std::uint32_t raw; + + static PackedInstanceId create(CodecId codecId, InstanceId instanceId) { + return {.raw = static_cast(codecId) << 15 | + static_cast(instanceId)}; } - // Data to be extracted from the header - uint8_t ver = (data[1] & 0x18) >> 3; // Version index - uint8_t lyr = (data[1] & 0x06) >> 1; // Layer index - uint8_t pad = (data[2] & 0x02) >> 1; // Padding? 0/1 - uint8_t brx = (data[2] & 0xf0) >> 4; // Bitrate index - uint8_t srx = (data[2] & 0x0c) >> 2; // SampRate index - - // Lookup real values of these fields - uint32_t bitrate = mpeg_bitrates[ver][lyr][brx] * 1000; - uint32_t samprate = mpeg_srates[ver][srx]; - uint16_t samples = mpeg_frame_samples[ver][lyr]; - uint8_t slot_size = mpeg_slot_size[lyr]; - - // In-between calculations - float bps = static_cast(samples) / 8.0f; - float fsize = - ((bps * static_cast(bitrate)) / static_cast(samprate)) + - ((pad) ? slot_size : 0); - - // ORBIS_LOG_TODO(__FUNCTION__, (uint16_t)ver, (uint16_t)lyr, - // (uint16_t)pad, (uint16_t)brx, (uint16_t)srx, bitrate, - // samprate, samples, (uint16_t)slot_size, bps, fsize, - // static_cast(fsize)); - - // Frame sizes are truncated integers - return static_cast(fsize); -} - -enum AACHeaderType { AAC_ADTS = 1, AAC_RAW = 2 }; - -inline constexpr orbis::uint32_t AACFreq[12] = {96000, 88200, 64000, 48000, - 44100, 32000, 24000, 22050, - 16000, 12000, 11025, 8000}; - -enum AJMCodecs : orbis::uint32_t { - AJM_CODEC_MP3 = 0, - AJM_CODEC_At9 = 1, - AJM_CODEC_AAC = 2, - - AJM_CODEC_COUNT -}; - -enum AJMChannels : orbis::uint32_t { - AJM_CHANNEL_DEFAULT = 0, - AJM_CHANNEL_1 = 1, - AJM_CHANNEL_2 = 2, - AJM_CHANNEL_3 = 3, - AJM_CHANNEL_4 = 4, - AJM_CHANNEL_5 = 5, - AJM_CHANNEL_6 = 6, - AJM_CHANNEL_8 = 8, -}; - -enum AJMFormat : orbis::uint32_t { - AJM_FORMAT_S16 = 0, // default - AJM_FORMAT_S32 = 1, - AJM_FORMAT_FLOAT = 2 -}; - -struct At9Instance { - orbis::ptr handle{}; - orbis::uint32_t inputChannels{}; - orbis::uint32_t framesInSuperframe{}; - orbis::uint32_t frameSamples{}; - orbis::uint32_t superFrameDataLeft{}; - orbis::uint32_t superFrameDataIdx{}; - orbis::uint32_t superFrameSize{}; - orbis::uint32_t estimatedSizeUsed{}; - orbis::uint32_t sampleRate{}; - Atrac9Format outputFormat{}; - orbis::uint32_t configData; - - ~At9Instance() { - if (handle) { - Atrac9ReleaseHandle(handle); - } + [[nodiscard]] InstanceId getInstanceId() const { + return static_cast(raw & ((1 << 15) - 1)); } + [[nodiscard]] CodecId getCodecId() const { + return static_cast(raw >> 15); + } + + auto operator<=>(const PackedInstanceId &) const = default; }; -struct AACInstance { - AACHeaderType headerType; - orbis::uint32_t sampleRate; -}; - -struct AJMSidebandGaplessDecode { +struct SidebandGaplessDecode { orbis::uint32_t totalSamples; orbis::uint16_t skipSamples; orbis::uint16_t totalSkippedSamples; }; -struct AJMSidebandResult { +struct SidebandResult { orbis::int32_t result; orbis::int32_t codecResult; }; -struct AJMSidebandStream { +struct SidebandStream { orbis::int32_t inputSize; orbis::int32_t outputSize; orbis::uint64_t decodedSamples; }; -struct AJMSidebandMultipleFrames { +struct SidebandMultipleFrames { orbis::uint32_t framesProcessed; orbis::uint32_t unk0; }; -struct AJMSidebandFormat { - AJMChannels channels; +struct SidebandFormat { + ChannelCount channels; orbis::uint32_t unk0; // maybe channel mask? orbis::uint32_t sampleRate; - AJMFormat sampleFormat; + Format sampleFormat; uint32_t bitrate; uint32_t unk1; }; -struct AJMAt9CodecInfoSideband { - orbis::uint32_t superFrameSize; - orbis::uint32_t framesInSuperFrame; - orbis::uint32_t unk0; - orbis::uint32_t frameSamples; +struct AjmBuffer { + orbis::ptr pData; + orbis::size_t size; }; -struct AJMMP3CodecInfoSideband { - orbis::uint32_t header; - orbis::uint8_t unk0; - orbis::uint8_t unk1; - orbis::uint8_t unk2; - orbis::uint8_t unk3; - orbis::uint8_t unk4; - orbis::uint8_t unk5; - orbis::uint16_t unk6; - orbis::uint16_t unk7; - orbis::uint16_t unk8; +struct Job { + std::uint64_t flags; + std::uint32_t sidebandSize; + std::uint32_t totalOutputSize; + std::vector inputBuffers; + std::vector outputBuffers; + orbis::ptr pSideband; + orbis::ptr pSidebandResult; + std::uint64_t controlFlags; }; -struct AJMAACCodecInfoSideband { - orbis::uint32_t heaac; - orbis::uint32_t unk0; -}; +class InstanceBatch; -struct Instance { - orbis::shared_mutex mtx; - AJMCodecs codec; - AJMChannels maxChannels; - AJMFormat outputFormat; - At9Instance at9; - AACInstance aac; - orbis::kvector inputBuffer; - orbis::kvector outputBuffer; +struct Batch : orbis::RcBase { + enum Status { + Queued, + Cancelled, + Complete, + }; - AVCodecContext *codecCtx; - SwrContext *resampler; - orbis::uint32_t lastBatchId; - // TODO: use AJMSidebandGaplessDecode for these variables - AJMSidebandGaplessDecode gapless; - orbis::uint32_t processedSamples; - AJMSidebandFormat lastDecode; +private: + std::atomic mCompleteJobs{0}; + orbis::shared_atomic32 mStatus{Status::Queued}; + std::atomic mBatchError{0}; + std::vector> mInstanceBatches; - ~Instance() { - if (resampler) { - swr_free(&resampler); +public: + [[nodiscard]] orbis::ErrorCode wait(std::uint32_t timeout, + std::uint64_t *batchError) { + std::chrono::microseconds usecTimeout; + if (timeout == std::numeric_limits::max()) { + usecTimeout = std::chrono::microseconds::max(); + } else { + usecTimeout = std::chrono::microseconds(timeout); } - if (codecCtx) { - avcodec_free_context(&codecCtx); + + while (true) { + auto status = mStatus.load(std::memory_order::relaxed); + if (status != Status::Queued) { + *batchError = mBatchError.load(std::memory_order::relaxed); + break; + } + + auto errc = mStatus.wait(status, usecTimeout); + + if (errc != std::errc{}) { + return orbis::toErrorCode(errc); + } + } + + return {}; + } + + [[nodiscard]] Status getStatus() const { + return Status(mStatus.load(std::memory_order::acquire)); + } + + void setStatus(Status status) { + if (status == Status::Queued) { + return; + } + + std::uint32_t prevStatus = Status::Queued; + if (mStatus.compare_exchange_strong(prevStatus, status, + std::memory_order::relaxed, + std::memory_order::release)) { + mStatus.notify_all(); + } + } + + void handleCompletion(std::uint64_t batchError) { + if (mBatchError != 0) { + std::uint64_t prevError = 0; + mBatchError.compare_exchange_strong(prevError, batchError); + } + + if (mCompleteJobs.fetch_add(1) == mInstanceBatches.size() - 1) { + setStatus(Status::Complete); } } }; -enum ControlFlags { - CONTROL_INITIALIZE = 0x4000, - CONTROL_RESET = 0x2000, +class InstanceBatch : public orbis::RcBase { + orbis::Ref mBatch; + std::vector mJobs; + +public: + InstanceBatch() = default; + InstanceBatch(orbis::Ref batch, std::vector jobs) + : mBatch(std::move(batch)), mJobs(std::move(jobs)) {} + + std::span getJobs() { return mJobs; } + void complete(std::uint64_t batchError) { + mBatch->handleCompletion(batchError); + } + [[nodiscard]] bool inProgress() const { + return mBatch->getStatus() == Batch::Status::Queued; + } }; -enum RunFlags { - RUN_MULTIPLE_FRAMES = 0x1000, - RUN_GET_CODEC_INFO = 0x800, +class CodecInstance : public orbis::RcBase { + std::mutex mWorkerMutex; + std::condition_variable mWorkerCv; + std::deque> mJobQueue; + std::jthread mWorkerThread{ + [this](const std::stop_token &stopToken) { workerEntry(stopToken); }}; + +public: + virtual ~CodecInstance() { + std::lock_guard lock(mWorkerMutex); + mWorkerThread.request_stop(); + mWorkerCv.notify_all(); + } + + virtual std::uint64_t runJob(const Job &job) = 0; + virtual void reset() = 0; + + void runBatch(orbis::Ref batch, std::vector jobs) { + auto instanceBatch = orbis::Ref( + orbis::knew(std::move(batch), std::move(jobs))); + + std::lock_guard lock(mWorkerMutex); + mJobQueue.push_back(instanceBatch); + mWorkerCv.notify_one(); + } + +private: + void workerEntry(const std::stop_token &stopToken) { + while (!stopToken.stop_requested()) { + orbis::Ref batch; + { + std::unique_lock lock(mWorkerMutex); + + while (mJobQueue.empty()) { + mWorkerCv.wait(lock); + + if (stopToken.stop_requested()) { + return; + } + } + } + + if (batch == nullptr) { + continue; + } + + if (batch->inProgress()) { + std::uint64_t error = 0; + for (auto &job : batch->getJobs()) { + auto result = runJob(job); + if (result != 0) { + error = result; + } + } + + batch->complete(error); + } + } + } }; -enum SidebandFlags { - SIDEBAND_STREAM = 0x800000000000, - SIDEBAND_FORMAT = 0x400000000000, - SIDEBAND_GAPLESS_DECODE = 0x200000000000, +struct Codec : orbis::RcBase { + virtual ~Codec() = default; + [[nodiscard]] virtual orbis::ErrorCode + createInstance(orbis::Ref *instance, std::uint32_t unk0, + std::uint64_t flags) = 0; +}; + +inline constexpr auto kCodecCount = rx::fieldCount; +} // namespace ajm + +struct AjmDevice : IoDevice { + orbis::shared_mutex mtx; + + orbis::Ref codecs[ajm::kCodecCount]; + orbis::RcIdMap + mCodecInstances[ajm::kCodecCount]; + + orbis::RcIdMap batchMap; + + orbis::ErrorCode open(orbis::Ref *file, const char *path, + std::uint32_t flags, std::uint32_t mode, + orbis::Thread *thread) override; + + template + void createCodec(ajm::CodecId id, ArgsT &&...args) { + auto instance = orbis::knew(std::forward(args)...); + codecs[std::to_underlying(id)] = instance; + } + + [[nodiscard]] orbis::ErrorCode + createInstance(orbis::Ref *instance, ajm::CodecId codecId, + std::uint32_t unk0, std::uint64_t flags) { + auto rawCodecId = std::to_underlying(codecId); + if (rawCodecId >= ajm::kCodecCount) { + return orbis::ErrorCode::INVAL; + } + + auto codec = codecs[rawCodecId]; + + if (codec == nullptr) { + return orbis::ErrorCode::SRCH; + } + + return codec->createInstance(instance, unk0, flags); + } + + [[nodiscard]] orbis::ErrorCode removeInstance(ajm::CodecId codecId, + ajm::InstanceId instanceId) { + auto rawCodecId = std::to_underlying(codecId); + if (rawCodecId >= ajm::kCodecCount) { + return orbis::ErrorCode::INVAL; + } + + if (!mCodecInstances[rawCodecId].close(instanceId)) { + return orbis::ErrorCode::BADF; + } + + return {}; + } + + [[nodiscard]] orbis::Ref + getInstance(ajm::CodecId codecId, ajm::InstanceId instanceId) { + auto rawCodecId = std::to_underlying(codecId); + if (rawCodecId >= ajm::kCodecCount) { + return {}; + } + + return mCodecInstances[rawCodecId].get(instanceId); + } + + [[nodiscard]] ajm::InstanceId + addCodecInstance(ajm::CodecId codecId, + orbis::Ref instance) { + auto &instances = mCodecInstances[std::to_underlying(codecId)]; + + auto id = instances.insert(std::move(instance)); + + if (id == std::remove_cvref_t::npos) { + std::println(stderr, "out of codec instances"); + std::abort(); + } + + return id; + } + + [[nodiscard]] ajm::BatchId addBatch(ajm::Batch *batch) { + auto id = batchMap.insert(batch); + if (id == decltype(batchMap)::npos) { + std::println(stderr, "out of batches"); + std::abort(); + } + + return id; + } + + [[nodiscard]] orbis::Ref getBatch(ajm::BatchId id) const { + return batchMap.get(id); + } + + [[nodiscard]] orbis::ErrorCode removeBatch(ajm::BatchId id) { + if (batchMap.close(id)) { + return orbis::ErrorCode::BADF; + } + + return {}; + } }; diff --git a/rpcsx/main.cpp b/rpcsx/main.cpp index 20373a46f..a581ac4e9 100644 --- a/rpcsx/main.cpp +++ b/rpcsx/main.cpp @@ -1,8 +1,11 @@ #include "audio/AlsaDevice.hpp" +#include "audio/At9Codec.hpp" +#include "audio/FFmpegCodecs.hpp" #include "backtrace.hpp" #include "gpu/DeviceCtl.hpp" #include "io-device.hpp" #include "io-devices.hpp" +#include "iodev/ajm.hpp" #include "iodev/mbus.hpp" #include "iodev/mbus_av.hpp" #include "ipmi.hpp" @@ -337,6 +340,10 @@ static void ps4InitDev() { auto analogAudioDevice = nullAudioDevice; auto spdifAudioDevice = nullAudioDevice; + auto ajm = static_cast(createAjmCharacterDevice()); + createAt9Codec(ajm); + createFFmpegCodecs(ajm); + vfs::addDevice("dmem0", createDmemCharacterDevice(0)); vfs::addDevice("npdrm", createNpdrmCharacterDevice()); vfs::addDevice("icc_configuration", createIccConfigurationCharacterDevice()); @@ -369,7 +376,7 @@ static void ps4InitDev() { vfs::addDevice("gc", createGcCharacterDevice()); vfs::addDevice("rng", createRngCharacterDevice()); vfs::addDevice("sbl_srv", createSblSrvCharacterDevice()); - vfs::addDevice("ajm", createAjmCharacterDevice()); + vfs::addDevice("ajm", ajm); vfs::addDevice("urandom", createUrandomCharacterDevice()); vfs::addDevice("mbus", mbus); vfs::addDevice("metadbg", createMetaDbgCharacterDevice());