diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 3cfeadf99..20a18fba0 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 f69e7f635..57309413c 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 3d4066b01..2ed668349 100644 --- a/rpcsx/iodev/ajm.cpp +++ b/rpcsx/iodev/ajm.cpp @@ -1,25 +1,649 @@ +#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 +#include +#include +#include +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +} struct AjmFile : orbis::File {}; +struct AjmDevice : IoDevice { + // TODO: remove when moving input and temp buffers to instance + orbis::shared_mutex mtx; + // TODO: move to intsance + orbis::kvector inputBuffer; + orbis::uint32_t inputSize = 0; + orbis::kvector tempBuffer; + + orbis::uint32_t batchId = 1; // temp + + orbis::uint32_t at9InstanceId = 0; + orbis::uint32_t mp3InstanceId = 0; + orbis::uint32_t aacInstanceId = 0; + 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()); + std::lock_guard lock(device->mtx); + // 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 = device->instanceMap.find(args->instanceId); + if (it != device->instanceMap.end()) { + auto &instance = device->instanceMap[args->instanceId]; + if (instance.resampler) { + swr_free(&instance.resampler); + } + if (instance.codecCtx) { + avcodec_free_context(&instance.codecCtx); + } + device->instanceMap.erase(it); + } + 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 + device->at9InstanceId++; + } else if (codecId == AJM_CODEC_MP3) { + args->instanceId = codecOffset + device->mp3InstanceId++; + } else if (codecId == AJM_CODEC_AAC) { + args->instanceId = codecOffset + device->aacInstanceId++; + } + Instance instance; + 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 { + // TODO: throw error + } + } + 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; + } + device->instanceMap.insert({ + args->instanceId, + instance, + }); + } else { + args->instanceId = codecOffset + device->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 = device->batchId; + // ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0, + // args->pBatch, args->batchSize, args->priority, + // args->batchError, args->batchId); + device->batchId += 1; + // thread->where(); + + 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 = device->instanceMap[instanceId]; + RunJob runJob{}; + device->inputSize = 0; + 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->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]; + }; + InitalizeBuffer *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 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]; + } + } + if (ctrl->getFlags() & SIDEBAND_GAPLESS_DECODE) { + struct InitalizeBuffer { + orbis::uint32_t totalSamples; + orbis::uint16_t skipSamples; + orbis::uint16_t totalSkippedSamples; + }; + InitalizeBuffer *initializeBuffer = + (InitalizeBuffer *)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: { + BatchJobInputBufferRa *job = (BatchJobInputBufferRa *)jobPtr; + // ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobInputBufferRa", + // job->opcode, job->szInputSize, job->pInput); + // We don't support split buffers for now, so ignore new buffers + if (device->inputSize == 0) { + std::memcpy(device->inputBuffer.data(), job->pInput, + job->szInputSize); + device->inputSize += job->szInputSize; + } + // rx::hexdump({(std::byte*) job->pInput, 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 = ((orbis::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->outputSize, job->pOutput); + runJob.pOutput = job->pOutput; + runJob.outputSize = job->outputSize; + 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 = device->inputSize; + stream->outputSize = runJob.outputSize; + } + } else if (!runJob.control) { + // orbis::uint32_t maxChannels = + // instance.maxChannels == AJM_CHANNEL_DEFAULT ? 2 + // : + // instance.maxChannels; + AJMSidebandResult *result = + reinterpret_cast(runJob.pSideband); + result->result = 0; + result->codecResult = 0; + + orbis::uint32_t inputReaded = 0; + orbis::uint32_t outputWritten = 0; + orbis::uint32_t framesProcessed = 0; + orbis::uint32_t samplesCount = 0; + if (device->inputSize != 0 && runJob.outputSize != 0) { + do { + if (instance.codec == AJM_CODEC_At9 && + instance.at9.frameSamples == 0) { + break; + } + if (inputReaded >= device->inputSize || + outputWritten >= runJob.outputSize) { + break; + } + + AVFrame *frame = av_frame_alloc(); + rx::atScopeExit _free_frame([&] { av_frame_free(&frame); }); + + orbis::uint32_t readed = 0; + orbis::uint32_t outputBufferSize = 0; + if (instance.codec == AJM_CODEC_At9) { + orbis::int32_t bytesUsed = 0; + outputBufferSize = av_samples_get_buffer_size( + nullptr, instance.at9.inputChannels, + instance.at9.frameSamples, + ajmToAvFormat(instance.outputFormat), 0); + int err = Atrac9Decode(instance.at9.handle, + &device->inputBuffer[inputReaded], + device->tempBuffer.data(), + instance.at9.outputFormat, &bytesUsed); + if (err != ERR_SUCCESS) { + rx::hexdump({(std::byte *)&device->inputBuffer[inputReaded], + device->inputSize}); + ORBIS_LOG_FATAL("Could not decode frame", err, + instance.at9.estimatedSizeUsed, + instance.at9.superFrameSize, + instance.at9.frameSamples, instance.at9.handle, + inputReaded, 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; + readed = 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) { + auto frameSize = get_mp3_data_size( + (orbis::uint8_t *)(&device->inputBuffer[inputReaded])); + if (frameSize == 0) { + frameSize = device->inputSize; + } else { + frameSize = std::min(frameSize, device->inputSize); + } + AVPacket *pkt = av_packet_alloc(); + rx::atScopeExit _free_pkt([&] { av_packet_free(&pkt); }); + pkt->data = (orbis::uint8_t *)(&device->inputBuffer[inputReaded]); + pkt->size = frameSize; + 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; + readed = frameSize; + instance.lastDecode.channels = + AJMChannels(frame->ch_layout.nb_channels); + instance.lastDecode.sampleRate = frame->sample_rate; + } else if (instance.codec == AJM_CODEC_AAC) { + AVPacket *pkt = av_packet_alloc(); + rx::atScopeExit _free_pkt([&] { av_packet_free(&pkt); }); + pkt->data = (orbis::uint8_t *)(&device->inputBuffer[inputReaded]); + pkt->size = device->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); + 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; + readed = len; + instance.lastDecode.channels = + AJMChannels(frame->ch_layout.nb_channels); + instance.lastDecode.sampleRate = frame->sample_rate; + } + framesProcessed += 1; + inputReaded += readed; + + if (instance.gapless.skipSamples > 0 || + instance.gapless.totalSamples > 0) { + if (instance.gapless.skipSamples > + instance.gapless.totalSkippedSamples || + 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) { + auto resampler = swr_alloc(); + 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(); + } + orbis::uint8_t *outputBuffer = + reinterpret_cast(device->tempBuffer.data()); + int nb_samples = + swr_convert(resampler, &outputBuffer, frame->nb_samples, + (const orbis::uint8_t **)frame->extended_data, + frame->nb_samples); + if (nb_samples < 0) { + ORBIS_LOG_FATAL("Error while converting"); + std::abort(); + } + } + memcpy(runJob.pOutput + outputWritten, device->tempBuffer.data(), + outputBufferSize); + outputWritten += outputBufferSize; + 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); + AJMSidebandStream *stream = reinterpret_cast( + runJob.pSideband + currentSize); + stream->inputSize = inputReaded; + stream->outputSize = outputWritten; + stream->decodedSamples = instance.processedSamples; + currentSize += sizeof(AJMSidebandStream); + } + + if (runJob.flags & SIDEBAND_FORMAT) { + // ORBIS_LOG_TODO("SIDEBAND_FORMAT", currentSize); + AJMSidebandFormat *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); + AJMSidebandGaplessDecode *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) { + 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", framesProcessed); + 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); + // thread->where(); + } else { + ORBIS_LOG_FATAL("Unhandled AJM ioctl", request); + thread->where(); + } return {}; } @@ -27,17 +651,18 @@ static const orbis::FileOps fileOps = { .ioctl = ajm_ioctl, }; -struct AjmDevice : IoDevice { - orbis::ErrorCode open(orbis::Ref *file, const char *path, - std::uint32_t flags, std::uint32_t mode, - orbis::Thread *thread) override { - auto newFile = orbis::knew(); - newFile->ops = &fileOps; - newFile->device = this; +orbis::ErrorCode AjmDevice::open(orbis::Ref *file, + const char *path, std::uint32_t flags, + std::uint32_t mode, orbis::Thread *thread) { + auto newFile = orbis::knew(); + newFile->ops = &fileOps; + newFile->device = this; - *file = newFile; - return {}; - } -}; + inputBuffer.reserve(32 * 1024); + tempBuffer.reserve(32 * 1024); + + *file = newFile; + return {}; +} IoDevice *createAjmCharacterDevice() { return orbis::knew(); } diff --git a/rpcsx/iodev/ajm.hpp b/rpcsx/iodev/ajm.hpp new file mode 100644 index 000000000..8ec3a7a65 --- /dev/null +++ b/rpcsx/iodev/ajm.hpp @@ -0,0 +1,339 @@ +#pragma once + +#include "libatrac9/libatrac9.h" +#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, +}; + +struct InstructionHeader { + orbis::uint32_t id; + orbis::uint32_t len; +}; + +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); + } +}; + +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; + std::byte *pSidebandInput; + orbis::uint32_t flagsHi; + orbis::uint32_t flagsLo; + orbis::uint32_t commandId; + orbis::uint32_t sidebandOutputSize; + std::byte *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; + std::byte *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; + std::byte *pOutput; +}; +static_assert(sizeof(BatchJobOutputBufferRa) == 0x10); + +struct BatchJobSidebandBufferRa { + orbis::uint32_t opcode; + orbis::uint32_t sidebandSize; + std::byte *pSideband; +}; +static_assert(sizeof(BatchJobSidebandBufferRa) == 0x10); + +struct RunJob { + orbis::uint64_t flags; + orbis::uint32_t outputSize; + std::byte *pOutput; + orbis::uint32_t sidebandSize; + std::byte *pSideband; + bool control; +}; + +// 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(__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 }; + +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_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; +}; + +struct AACInstance { + AACHeaderType headerType; + orbis::uint32_t sampleRate; +}; + +struct AJMSidebandGaplessDecode { + orbis::uint32_t totalSamples; + orbis::uint16_t skipSamples; + orbis::uint16_t totalSkippedSamples; +}; + +struct AJMSidebandResult { + orbis::int32_t result; + orbis::int32_t codecResult; +}; + +struct AJMSidebandStream { + orbis::int32_t inputSize; + orbis::int32_t outputSize; + orbis::uint64_t decodedSamples; +}; + +struct AJMSidebandMultipleFrames { + orbis::uint32_t framesProcessed; + orbis::uint32_t unk0; +}; + +struct AJMSidebandFormat { + AJMChannels channels; + orbis::uint32_t unk0; // maybe channel mask? + orbis::uint32_t sampleRate; + AJMFormat 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 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 AJMAACCodecInfoSideband { + orbis::uint32_t heaac; + orbis::uint32_t unk0; +}; + +struct Instance { + AJMCodecs codec; + AJMChannels maxChannels; + AJMFormat outputFormat; + At9Instance at9; + AACInstance aac; + AVCodecContext *codecCtx; + SwrContext *resampler; + orbis::uint32_t lastBatchId; + // TODO: use AJMSidebandGaplessDecode for these variables + AJMSidebandGaplessDecode gapless; + orbis::uint32_t processedSamples; + AJMSidebandFormat lastDecode; +}; + +enum ControlFlags { + CONTROL_INITIALIZE = 0x4000, + CONTROL_RESET = 0x2000, +}; + +enum RunFlags { + RUN_MULTIPLE_FRAMES = 0x1000, + RUN_GET_CODEC_INFO = 0x800, +}; + +enum SidebandFlags { + SIDEBAND_STREAM = 0x800000000000, + SIDEBAND_FORMAT = 0x400000000000, + SIDEBAND_GAPLESS_DECODE = 0x200000000000, +}; \ No newline at end of file