diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 3cfeadf..20a18fb 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -36,7 +36,7 @@ add_subdirectory(LibAtrac9) set(FFMPEG_PATH ${CMAKE_CURRENT_SOURCE_DIR}/FFmpeg) add_custom_command( OUTPUT ${FFMPEG_PATH}/config.h - COMMAND ./configure + COMMAND ./configure --disable-libdrm --disable-vaapi --disable-vdpau --disable-zlib --disable-lzma COMMENT "Configuring FFmpeg..." WORKING_DIRECTORY ${FFMPEG_PATH} ) diff --git a/rpcsx/CMakeLists.txt b/rpcsx/CMakeLists.txt index f69e7f6..5730941 100644 --- a/rpcsx/CMakeLists.txt +++ b/rpcsx/CMakeLists.txt @@ -78,6 +78,7 @@ PUBLIC ffmpeg::avcodec ffmpeg::swresample ffmpeg::avutil + Atrac9 rpcsx-gpu orbis::kernel rx diff --git a/rpcsx/iodev/ajm.cpp b/rpcsx/iodev/ajm.cpp index 3d4066b..e2e2c97 100644 --- a/rpcsx/iodev/ajm.cpp +++ b/rpcsx/iodev/ajm.cpp @@ -1,25 +1,661 @@ +#include "ajm.hpp" #include "io-device.hpp" +#include "libatrac9/libatrac9.h" +#include "orbis-config.hpp" #include "orbis/KernelAllocator.hpp" #include "orbis/file.hpp" #include "orbis/thread/Thread.hpp" #include "orbis/utils/Logs.hpp" +#include +#include +#include +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +} struct AjmFile : orbis::File {}; +uint batchId = 1; + +orbis::uint32_t at9InstanceId = 0; +orbis::uint32_t mp3InstanceId = 0; +orbis::uint32_t aacInstanceId = 0; +orbis::uint32_t unimplementedInstanceId = 0; +std::map instanceMap; + +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; + } +} + static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, void *argp, orbis::Thread *thread) { + // 0xc0288900 - finalize // 0xc0288903 - module register // 0xc0288904 - module unregister - if (request == 0xc0288903 || request == 0xc0288904) { + // 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); + auto it = instanceMap.find(args->instanceId); + if (it != instanceMap.end()) { + auto &instance = instanceMap[args->instanceId]; + if (instance.resampler) { + swr_free(&instance.resampler); + avcodec_free_context(&instance.codecCtx); + } + instanceMap.erase(args->instanceId); + } + args->result = 0; + } + 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) { + struct InstanceCreateArgs { + orbis::uint32_t result; + orbis::uint32_t unk0; + orbis::uint64_t flags; + orbis::uint32_t codec; + orbis::uint32_t instanceId; + }; + auto args = reinterpret_cast(argp); + AJMCodecs codecId = AJMCodecs(args->codec); + auto codecOffset = codecId << 0xe; + if (codecId >= 0 && codecId <= 2) { + args->result = 0; + if (codecId == AJM_CODEC_At9) { + args->instanceId = codecOffset + at9InstanceId++; + } else if (codecId == AJM_CODEC_MP3) { + args->instanceId = codecOffset + mp3InstanceId++; + } else if (codecId == AJM_CODEC_AAC) { + args->instanceId = codecOffset + aacInstanceId++; + } + Instance instance; + instance.codec = codecId; + instance.outputChannels = + AJMChannels(((args->flags & ~7) & (0xFF & ~0b11)) >> 3); + instance.outputFormat = AJMFormat((args->flags & ~7) & 0b11); + if (codecId == AJM_CODEC_At9) { + instance.at9.handle = Atrac9GetHandle(); + } + 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(); + } - ORBIS_LOG_FATAL("Unhandled AJM ioctl", request); - thread->where(); + if (int err = avcodec_open2(codecCtx, codec, NULL) < 0) { + ORBIS_LOG_FATAL("Could not open codec"); + std::abort(); + } + + instance.codecCtx = codecCtx; + } + instanceMap.insert({ + args->instanceId, + instance, + }); + } else { + args->instanceId = codecOffset + unimplementedInstanceId++; + } + ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0, + args->flags, args->codec, args->instanceId); + } else if (request == 0xc0288907) { + struct StartBatchBufferArgs { + 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; + }; + auto args = reinterpret_cast(argp); + args->result = 0; + args->batchId = batchId; + ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0, + args->pBatch, args->batchSize, args->priority, + args->batchError, args->batchId); + batchId += 1; + + auto ptr = args->pBatch; + auto endPtr = args->pBatch + args->batchSize; + + while (ptr < endPtr) { + auto header = (InstructionHeader *)ptr; + auto instanceId = (header->id >> 6) & 0xfffff; + auto jobPtr = ptr + sizeof(InstructionHeader); + auto endJobPtr = ptr + header->len; + // TODO: handle unimplemented codecs, so auto create instance for now + auto &instance = instanceMap[instanceId]; + 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; + BatchJobControlBufferRa *ctrl = (BatchJobControlBufferRa *)jobPtr; + AJMSidebandResult *result = + reinterpret_cast(ctrl->pSidebandOutput); + result->result = 0; + result->codecResult = 0; + ORBIS_LOG_ERROR(__FUNCTION__, request, "control buffer", ctrl->opcode, + ctrl->commandId, ctrl->flagsHi, ctrl->flagsLo, + ctrl->sidebandInputSize, ctrl->sidebandOutputSize); + if ((ctrl->flagsLo & ~7) & CONTROL_INITIALIZE) { + if (instance.codec == AJM_CODEC_At9) { + struct InitalizeBuffer { + orbis::uint32_t configData; + orbis::int32_t unk0[2]; + }; + InitalizeBuffer *initializeBuffer = + (InitalizeBuffer *)ctrl->pSidebandInput; + int err = Atrac9InitDecoder( + instance.at9.handle, + reinterpret_cast(&initializeBuffer->configData)); + if (err < 0) { + ORBIS_LOG_FATAL("AT9 Init Decoder error", err); + rx::hexdump({(std::byte *)ctrl->pSidebandInput, + ctrl->sidebandInputSize}); + 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; + + orbis::uint32_t outputChannels = + instance.outputChannels == AJM_DEFAULT + ? instance.at9.inputChannels + : instance.outputChannels; + if (instance.at9.inputChannels != outputChannels || + instance.outputFormat != AJM_FORMAT_S16) { + instance.resampler = swr_alloc(); + if (!instance.resampler) { + ORBIS_LOG_FATAL("Could not allocate resampler context"); + std::abort(); + } + + AVChannelLayout inputChLayout; + av_channel_layout_default(&inputChLayout, + instance.at9.inputChannels); + + AVChannelLayout outputChLayout; + av_channel_layout_default(&outputChLayout, outputChannels); + + av_opt_set_chlayout(instance.resampler, "in_chlayout", + &inputChLayout, 0); + av_opt_set_chlayout(instance.resampler, "out_chlayout", + &outputChLayout, 0); + av_opt_set_int(instance.resampler, "in_sample_rate", + pCodecInfo.samplingRate, 0); + av_opt_set_int(instance.resampler, "out_sample_rate", + pCodecInfo.samplingRate, 0); + av_opt_set_sample_fmt(instance.resampler, "in_sample_fmt", + ajmToAvFormat(AJM_FORMAT_S16), 0); + av_opt_set_sample_fmt(instance.resampler, "out_sample_fmt", + ajmToAvFormat(instance.outputFormat), 0); + if (swr_init(instance.resampler) < 0) { + ORBIS_LOG_FATAL( + "Failed to initialize the resampling context"); + std::abort(); + } + } + } else if (instance.codec == AJM_CODEC_AAC) { + struct InitalizeBuffer { + orbis::uint32_t headerIndex; + orbis::uint32_t sampleRateIndex; + }; + InitalizeBuffer *initializeBuffer = + (InitalizeBuffer *)ctrl->pSidebandInput; + instance.aac.headerType = + AACHeaderType(initializeBuffer->headerIndex); + instance.aac.sampleRate = + AACFreq[initializeBuffer->sampleRateIndex]; + } + } + jobPtr += sizeof(BatchJobControlBufferRa); + break; + } + case Opcode::RunBufferRa: { + BatchJobInputBufferRa *job = (BatchJobInputBufferRa *)jobPtr; + ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobInputBufferRa", + job->opcode, job->szInputSize, job->pInput); + runJob.pInput = job->pInput; + runJob.inputSize = job->szInputSize; + jobPtr += sizeof(BatchJobInputBufferRa); + break; + } + case Opcode::Flags: { + BatchJobFlagsRa *job = (BatchJobFlagsRa *)jobPtr; + ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobFlagsRa", + job->flagsHi, job->flagsLo); + runJob.flags = ((uint64_t)job->flagsHi << 0x1a) | job->flagsLo; + jobPtr += sizeof(BatchJobFlagsRa); + break; + } + case Opcode::JobBufferOutputRa: { + BatchJobOutputBufferRa *job = (BatchJobOutputBufferRa *)jobPtr; + ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobOutputBufferRa", + job->opcode, job->szOutputSize, job->pOutput); + runJob.pOutput = job->pOutput; + runJob.outputSize = job->szOutputSize; + jobPtr += sizeof(BatchJobOutputBufferRa); + break; + } + case Opcode::JobBufferSidebandRa: { + BatchJobSidebandBufferRa *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; + } + } + ptr = jobPtr; + if (!runJob.control && instanceId >= 0xC000) { + AJMSidebandResult *result = + reinterpret_cast(runJob.pSideband); + result->result = 0; + result->codecResult = 0; + if (runJob.flags & SIDEBAND_STREAM) { + AJMSidebandStream *stream = + reinterpret_cast(runJob.pSideband + 8); + stream->inputSize = runJob.inputSize; + stream->outputSize = runJob.outputSize; + } + } else if (!runJob.control) { + orbis::uint32_t outputChannels = instance.outputChannels == AJM_DEFAULT + ? 2 + : instance.outputChannels; + AJMSidebandResult *result = + reinterpret_cast(runJob.pSideband); + result->result = 0; + result->codecResult = 0; + + uint32_t inputReaded = 0; + uint32_t outputWritten = 0; + uint32_t framesProcessed = 0; + uint32_t channels = 0; + uint32_t sampleRate = 0; + if (runJob.inputSize != 0 && runJob.outputSize != 0) { + while (inputReaded < runJob.inputSize && + outputWritten < runJob.outputSize) { + // TODO: initialize if not + if (instance.at9.frameSamples == 0 && + instance.codec == AJM_CODEC_At9) { + break; + } + if (instance.codec == AJM_CODEC_At9) { + outputChannels = instance.outputChannels == AJM_DEFAULT + ? instance.at9.inputChannels + : instance.outputChannels; + orbis::int32_t outputBufferSize = av_samples_get_buffer_size( + nullptr, outputChannels, instance.at9.frameSamples, + ajmToAvFormat(instance.resampler ? AJM_FORMAT_S16 + : instance.outputFormat), + 0); + + orbis::uint8_t *tempBuffer = + instance.resampler ? (uint8_t *)av_malloc(outputBufferSize) + : reinterpret_cast( + runJob.pOutput + outputWritten); + orbis::int32_t bytesUsed = 0; + int err = + Atrac9Decode(instance.at9.handle, runJob.pInput + inputReaded, + tempBuffer, kAtrac9FormatS16, &bytesUsed); + if (err != ERR_SUCCESS) { + ORBIS_LOG_FATAL("Could not decode frame", err); + std::abort(); + } + if (instance.resampler) { + auto outputBuffer = reinterpret_cast( + runJob.pOutput + outputWritten); + + int nb_samples = + swr_convert(instance.resampler, &outputBuffer, + instance.at9.frameSamples, &tempBuffer, + instance.at9.frameSamples); + if (nb_samples < 0) { + ORBIS_LOG_FATAL("Error while resampling"); + std::abort(); + } + av_freep(&tempBuffer); + } + 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; + } + channels = instance.at9.inputChannels; + sampleRate = instance.at9.sampleRate; + inputReaded += instance.at9.estimatedSizeUsed; + outputWritten += + std::max((uint32_t)outputBufferSize, runJob.outputSize); + framesProcessed += 1; + } else if (instance.codec == AJM_CODEC_MP3) { + ORBIS_LOG_FATAL("Pre get mp3 data size info", runJob.inputSize, + runJob.outputSize, runJob.sidebandSize, + runJob.flags); + auto realInputSize = + get_mp3_data_size((uint8_t *)(runJob.pInput + inputReaded)); + if (realInputSize == 0) { + realInputSize = runJob.inputSize; + } else { + realInputSize = std::min(realInputSize, runJob.inputSize); + } + + if (inputReaded + realInputSize > runJob.inputSize) { + break; + } + + // rx::hexdump( + // {(std::byte *)(runJob.pInput + inputReaded), + // realInputSize}); + + AVPacket *pkt = av_packet_alloc(); + AVFrame *frame = av_frame_alloc(); + pkt->data = (uint8_t *)(runJob.pInput + inputReaded); + pkt->size = realInputSize; + 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(); + } + + auto resampler = swr_alloc(); + if (!resampler) { + ORBIS_LOG_FATAL("Could not allocate resampler context"); + std::abort(); + } + + AVChannelLayout inputChLayout; + av_channel_layout_default(&inputChLayout, + frame->ch_layout.nb_channels); + + AVChannelLayout outputChLayout; + av_channel_layout_default(&outputChLayout, outputChannels); + + av_opt_set_chlayout(resampler, "in_chlayout", &inputChLayout, 0); + av_opt_set_chlayout(resampler, "out_chlayout", &outputChLayout, + 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(); + } + + uint8_t *outputBuffer = NULL; + int outputBufferSize = av_samples_alloc( + &outputBuffer, NULL, frame->ch_layout.nb_channels, + frame->nb_samples, ajmToAvFormat(instance.outputFormat), 0); + if (outputBufferSize < 0) { + ORBIS_LOG_FATAL("Could not allocate output buffer"); + std::abort(); + } + ORBIS_LOG_TODO("output buffer info", frame->ch_layout.nb_channels, + frame->nb_samples, (int32_t)instance.outputFormat, + outputBufferSize); + + if (outputWritten + outputBufferSize > runJob.outputSize) { + ORBIS_LOG_TODO("overwriting", outputWritten, outputBufferSize, + outputWritten + outputBufferSize, + runJob.outputSize); + break; + } + + int nb_samples = + swr_convert(resampler, &outputBuffer, frame->nb_samples, + (const uint8_t **)frame->data, frame->nb_samples); + if (nb_samples < 0) { + ORBIS_LOG_FATAL("Error while converting"); + std::abort(); + } + + memcpy(runJob.pOutput + outputWritten, outputBuffer, + outputBufferSize); + channels = frame->ch_layout.nb_channels; + sampleRate = frame->sample_rate; + inputReaded += realInputSize; + outputWritten += outputBufferSize; + framesProcessed += 1; + av_freep(&outputBuffer); + swr_free(&resampler); + av_frame_free(&frame); + av_packet_free(&pkt); + } else if (instance.codec == AJM_CODEC_AAC) { + AVPacket *pkt = av_packet_alloc(); + AVFrame *frame = av_frame_alloc(); + pkt->data = (uint8_t *)runJob.pInput + inputReaded; + pkt->size = runJob.inputSize; + + // 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); + + orbis::uint32_t outputChannels = + instance.outputChannels == AJM_DEFAULT + ? frame->ch_layout.nb_channels + : instance.outputChannels; + + ORBIS_LOG_TODO("aac decode", len, gotFrame, + frame->ch_layout.nb_channels, frame->sample_rate, + instance.aac.sampleRate, outputChannels, + (orbis::uint32_t)instance.outputChannels); + + auto resampler = swr_alloc(); + if (!resampler) { + ORBIS_LOG_FATAL("Could not allocate resampler context"); + std::abort(); + } + + AVChannelLayout inputChLayout; + av_channel_layout_default(&inputChLayout, + frame->ch_layout.nb_channels); + + AVChannelLayout outputChLayout; + av_channel_layout_default(&outputChLayout, outputChannels); + + av_opt_set_chlayout(resampler, "in_chlayout", &inputChLayout, 0); + av_opt_set_chlayout(resampler, "out_chlayout", &outputChLayout, + 0); + av_opt_set_int(resampler, "in_sample_rate", + instance.aac.sampleRate, 0); + av_opt_set_int(resampler, "out_sample_rate", + instance.aac.sampleRate, 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(); + } + + uint8_t *outputBuffer = NULL; + int outputBufferSize = av_samples_alloc( + &outputBuffer, NULL, outputChannels, frame->nb_samples, + ajmToAvFormat(instance.outputFormat), 0); + if (outputBufferSize < 0) { + ORBIS_LOG_FATAL("Could not allocate output buffer"); + std::abort(); + } + + int nb_samples = + swr_convert(resampler, &outputBuffer, frame->nb_samples, + frame->extended_data, frame->nb_samples); + if (nb_samples < 0) { + ORBIS_LOG_FATAL("Error while converting"); + std::abort(); + } + + memcpy(runJob.pOutput + outputWritten, outputBuffer, + outputBufferSize); + channels = frame->ch_layout.nb_channels; + sampleRate = frame->sample_rate; + inputReaded += len; + outputWritten += outputBufferSize; + framesProcessed += 1; + av_frame_free(&frame); + av_packet_free(&pkt); + swr_free(&resampler); + } + if (!(runJob.flags & RUN_MULTIPLE_FRAMES)) { + break; + } + } + } + + orbis::int64_t currentSize = sizeof(AJMSidebandResult); + + if (runJob.flags & SIDEBAND_STREAM) { + ORBIS_LOG_TODO("SIDEBAND_STREAM", currentSize, inputReaded, + outputWritten); + AJMSidebandStream *stream = reinterpret_cast( + runJob.pSideband + currentSize); + stream->inputSize = inputReaded; + stream->outputSize = outputWritten; + currentSize += sizeof(AJMSidebandStream); + } + + if (runJob.flags & SIDEBAND_FORMAT) { + ORBIS_LOG_TODO("SIDEBAND_FORMAT", currentSize); + AJMSidebandFormat *format = reinterpret_cast( + runJob.pSideband + currentSize); + format->channels = AJMChannels(channels); + format->sampleRate = sampleRate; + format->sampleFormat = AJM_FORMAT_FLOAT; + currentSize += sizeof(AJMSidebandFormat); + } + + if (runJob.flags & RUN_GET_CODEC_INFO) { + ORBIS_LOG_TODO("RUN_GET_CODEC_INFO"); + if (instance.codec == AJM_CODEC_At9) { + AJMAt9CodecInfoSideband *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 + AJMMP3CodecInfoSideband *info = + reinterpret_cast(runJob.pSideband + + currentSize); + currentSize += sizeof(AJMMP3CodecInfoSideband); + } else if (instance.codec == AJM_CODEC_AAC) { + // TODO + AJMAACCodecInfoSideband *info = + reinterpret_cast(runJob.pSideband + + currentSize); + currentSize += sizeof(AJMAACCodecInfoSideband); + } + } + + if (runJob.flags & RUN_MULTIPLE_FRAMES) { + ORBIS_LOG_TODO("RUN_MULTIPLE_FRAMES", currentSize); + AJMSidebandMultipleFrames *multipleFrames = + reinterpret_cast(runJob.pSideband + + currentSize); + multipleFrames->framesProcessed = framesProcessed; + currentSize += sizeof(AJMSidebandMultipleFrames); + } + } + } + + } else if (request == 0xc0288908) { + struct Args { + orbis::uint32_t unk0; + orbis::uint32_t unk1; + orbis::uint32_t batchId; + orbis::uint32_t timeout; + orbis::uint64_t batchError; + }; + auto args = reinterpret_cast(argp); + args->unk0 = 0; + ORBIS_LOG_ERROR(__FUNCTION__, request, args->unk0, args->unk1, + args->batchId, args->timeout, args->batchError); + } else { + ORBIS_LOG_FATAL("Unhandled AJM ioctl", request); + thread->where(); + } return {}; } diff --git a/rpcsx/iodev/ajm.hpp b/rpcsx/iodev/ajm.hpp new file mode 100644 index 0000000..b5f9cbb --- /dev/null +++ b/rpcsx/iodev/ajm.hpp @@ -0,0 +1,322 @@ +#include "orbis-config.hpp" +#include "orbis/utils/Logs.hpp" +#include +extern "C" { +#include +#include +#include +} + +enum class Opcode : std::uint8_t { + RunBufferRa = 1, + ControlBufferRa = 2, + Flags = 4, + ReturnAddress = 6, + JobBufferOutputRa = 17, + JobBufferSidebandRa = 18, +}; + +typedef struct InstructionHeader { + orbis::uint32_t id; + orbis::uint32_t len; +} InstructionHeader; + +static_assert(sizeof(InstructionHeader) == 0x8); + +typedef 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); + } +} OpcodeHeader; + +typedef struct ReturnAddress { + orbis::uint32_t opcode; + orbis::uint32_t unk; // 0, padding? + orbis::ptr returnAddress; +} ReturnAddress; +static_assert(sizeof(ReturnAddress) == 0x10); + +typedef struct BatchJobControlBufferRa { + orbis::uint32_t opcode; + orbis::uint32_t sidebandInputSize; + std::byte* pSidebandInput; + orbis::uint32_t flagsHi; + orbis::uint32_t flagsLo; + orbis::uint32_t commandId; + orbis::uint32_t sidebandOutputSize; + std::byte* pSidebandOutput; +} BatchJobControlBufferRa; +static_assert(sizeof(BatchJobControlBufferRa) == 0x28); + +typedef struct BatchJobInputBufferRa { + orbis::uint32_t opcode; + orbis::uint32_t szInputSize; + std::byte* pInput; +} BatchJobInputBufferRa; +static_assert(sizeof(BatchJobInputBufferRa) == 0x10); + +typedef struct BatchJobFlagsRa { + orbis::uint32_t flagsHi; + orbis::uint32_t flagsLo; +} BatchJobFlagsRa; + +static_assert(sizeof(BatchJobFlagsRa) == 0x8); + +typedef struct BatchJobOutputBufferRa { + orbis::uint32_t opcode; + orbis::uint32_t szOutputSize; + std::byte* pOutput; +} BatchJobOutputBufferRa; +static_assert(sizeof(BatchJobOutputBufferRa) == 0x10); + +typedef struct BatchJobSidebandBufferRa { + orbis::uint32_t opcode; + orbis::uint32_t sidebandSize; + std::byte* pSideband; +} BatchJobSidebandBufferRa; +static_assert(sizeof(BatchJobSidebandBufferRa) == 0x10); + +typedef struct RunJob { + orbis::uint64_t flags; + orbis::uint32_t inputSize; + std::byte* pInput; + orbis::uint32_t outputSize; + std::byte* pOutput; + orbis::uint32_t sidebandSize; + std::byte* pSideband; + bool control; +} RunJob; + +// Thanks to mystical SirNickity with 1 post +// https://hydrogenaud.io/index.php?topic=85125.msg747716#msg747716 + +const uint8_t mpeg_versions[4] = {25, 0, 2, 1}; + +// Layers - use [layer] +const uint8_t mpeg_layers[4] = {0, 3, 2, 1}; + +// Bitrates - use [version][layer][bitrate] +const 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] +const 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] +const 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] +const uint8_t mpeg_slot_size[4] = {0, 1, 1, 4}; // Rsvd, 3, 2, 1 + +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("get_mp3_data_size", (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 }; + +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, +}; + +enum AJMChannels : orbis::uint32_t { + AJM_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 +}; + +typedef 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; +} At9Instance; + +typedef struct AACInstance { + AACHeaderType headerType; + orbis::uint32_t sampleRate; +} AACInstance; + +typedef struct Instance { + AJMCodecs codec; + AJMChannels outputChannels; + AJMFormat outputFormat; + At9Instance at9; + AACInstance aac; + AVCodecContext *codecCtx; + SwrContext *resampler; + orbis::uint32_t lastBatchId; +} Instance; + +typedef struct AJMSidebandResult { + orbis::int32_t result; + orbis::int32_t codecResult; +} AJMSidebandResult; + +typedef struct AJMSidebandStream { + orbis::int32_t inputSize; + orbis::int32_t outputSize; + orbis::uint64_t unk0; +} AJMSidebandStream; + +typedef struct AJMSidebandMultipleFrames { + orbis::uint32_t framesProcessed; + orbis::uint32_t unk0; +} AJMSidebandMultipleFrames; + +typedef struct AJMSidebandFormat { + AJMChannels channels; + orbis::uint32_t unk0; // maybe channel mask? + orbis::uint32_t sampleRate; + AJMFormat sampleFormat; + uint32_t bitrate; + uint32_t unk1; +} AJMSidebandFormat; + +typedef struct AJMAt9CodecInfoSideband { + orbis::uint32_t superFrameSize; + orbis::uint32_t framesInSuperFrame; + orbis::uint32_t unk0; + orbis::uint32_t frameSamples; +} AJMAt9CodecInfoSideband; + +typedef 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; +} AJMMP3CodecInfoSideband; + +typedef struct AJMAACCodecInfoSideband { + orbis::uint32_t heaac; + orbis::uint32_t unk0; +} AJMAACCodecInfoSideband; + +enum ControlFlags { + CONTROL_INITIALIZE = 0x4000, +}; + +enum RunFlags { + RUN_MULTIPLE_FRAMES = 0x1000, + RUN_GET_CODEC_INFO = 0x800, +}; + +enum SidebandFlags { + SIDEBAND_STREAM = 0x800000000000, + SIDEBAND_FORMAT = 0x400000000000 +}; \ No newline at end of file