From 0a898a507aa26469bc9bb23120a6cdde1b3fc94e Mon Sep 17 00:00:00 2001 From: Nikita Savyolov Date: Fri, 18 Oct 2024 22:36:51 +0300 Subject: [PATCH 1/5] rpcsx: ajm decode draft --- 3rdparty/CMakeLists.txt | 2 +- rpcsx/CMakeLists.txt | 1 + rpcsx/iodev/ajm.cpp | 642 +++++++++++++++++++++++++++++++++++++++- rpcsx/iodev/ajm.hpp | 322 ++++++++++++++++++++ 4 files changed, 963 insertions(+), 4 deletions(-) create mode 100644 rpcsx/iodev/ajm.hpp 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..e2e2c9769 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 000000000..b5f9cbba5 --- /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 From 8073b49bea24d36bc236ae83afa8a0c086c17c62 Mon Sep 17 00:00:00 2001 From: Nikita Savyolov Date: Sun, 20 Oct 2024 14:20:44 +0300 Subject: [PATCH 2/5] rpcsx: ajm gapless decode wip --- rpcsx/iodev/ajm.cpp | 247 +++++++++++++++++++++++++++++++++----------- rpcsx/iodev/ajm.hpp | 106 +++++++++++-------- 2 files changed, 252 insertions(+), 101 deletions(-) diff --git a/rpcsx/iodev/ajm.cpp b/rpcsx/iodev/ajm.cpp index e2e2c9769..71f6147ff 100644 --- a/rpcsx/iodev/ajm.cpp +++ b/rpcsx/iodev/ajm.cpp @@ -8,6 +8,7 @@ #include "orbis/utils/Logs.hpp" #include #include +#include #include extern "C" { #include @@ -23,13 +24,15 @@ extern "C" { struct AjmFile : orbis::File {}; -uint batchId = 1; +namespace ajm { +orbis::uint32_t 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; +orbis::kmap instanceMap; +} // namespace ajm AVSampleFormat ajmToAvFormat(AJMFormat ajmFormat) { switch (ajmFormat) { @@ -64,14 +67,14 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, orbis::uint32_t instanceId; }; auto args = reinterpret_cast(argp); - auto it = instanceMap.find(args->instanceId); - if (it != instanceMap.end()) { - auto &instance = instanceMap[args->instanceId]; + auto it = ajm::instanceMap.find(args->instanceId); + if (it != ajm::instanceMap.end()) { + auto &instance = ajm::instanceMap[args->instanceId]; if (instance.resampler) { swr_free(&instance.resampler); avcodec_free_context(&instance.codecCtx); } - instanceMap.erase(args->instanceId); + ajm::instanceMap.erase(args->instanceId); } args->result = 0; } @@ -95,15 +98,15 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, if (codecId >= 0 && codecId <= 2) { args->result = 0; if (codecId == AJM_CODEC_At9) { - args->instanceId = codecOffset + at9InstanceId++; + args->instanceId = codecOffset + ajm::at9InstanceId++; } else if (codecId == AJM_CODEC_MP3) { - args->instanceId = codecOffset + mp3InstanceId++; + args->instanceId = codecOffset + ajm::mp3InstanceId++; } else if (codecId == AJM_CODEC_AAC) { - args->instanceId = codecOffset + aacInstanceId++; + args->instanceId = codecOffset + ajm::aacInstanceId++; } Instance instance; instance.codec = codecId; - instance.outputChannels = + instance.maxChannels = AJMChannels(((args->flags & ~7) & (0xFF & ~0b11)) >> 3); instance.outputFormat = AJMFormat((args->flags & ~7) & 0b11); if (codecId == AJM_CODEC_At9) { @@ -129,12 +132,12 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, instance.codecCtx = codecCtx; } - instanceMap.insert({ + ajm::instanceMap.insert({ args->instanceId, instance, }); } else { - args->instanceId = codecOffset + unimplementedInstanceId++; + args->instanceId = codecOffset + ajm::unimplementedInstanceId++; } ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0, args->flags, args->codec, args->instanceId); @@ -150,11 +153,11 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, }; auto args = reinterpret_cast(argp); args->result = 0; - args->batchId = batchId; + args->batchId = ajm::batchId; ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0, args->pBatch, args->batchSize, args->priority, args->batchError, args->batchId); - batchId += 1; + ajm::batchId += 1; auto ptr = args->pBatch; auto endPtr = args->pBatch + args->batchSize; @@ -165,7 +168,7 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, auto jobPtr = ptr + sizeof(InstructionHeader); auto endJobPtr = ptr + header->len; // TODO: handle unimplemented codecs, so auto create instance for now - auto &instance = instanceMap[instanceId]; + auto &instance = ajm::instanceMap[instanceId]; RunJob runJob; while (jobPtr < endJobPtr) { auto typed = (OpcodeHeader *)jobPtr; @@ -187,7 +190,17 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, 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 (ctrl->getFlags() & CONTROL_RESET) { + // instance.outputChannels = AJMChannels(0); + // instance.outputFormat = AJMFormat(0); + instance.gaplessSkipSamples = 0; + instance.gaplessTotalSamples = 0; + instance.gaplessTotalSkippedSamples = 0; + instance.processedSamples = 0; + ORBIS_LOG_TODO("CONTROL_RESET"); + } + + if (ctrl->getFlags() & CONTROL_INITIALIZE) { if (instance.codec == AJM_CODEC_At9) { struct InitalizeBuffer { orbis::uint32_t configData; @@ -215,10 +228,14 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, instance.at9.superFrameDataLeft = pCodecInfo.superframeSize; instance.at9.sampleRate = pCodecInfo.samplingRate; + orbis::uint32_t maxChannels = + instance.maxChannels == AJM_CHANNEL_DEFAULT + ? 2 + : instance.maxChannels; orbis::uint32_t outputChannels = - instance.outputChannels == AJM_DEFAULT - ? instance.at9.inputChannels - : instance.outputChannels; + instance.at9.inputChannels > maxChannels + ? maxChannels + : instance.at9.inputChannels; if (instance.at9.inputChannels != outputChannels || instance.outputFormat != AJM_FORMAT_S16) { instance.resampler = swr_alloc(); @@ -265,6 +282,24 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, 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.gaplessTotalSamples = initializeBuffer->totalSamples; + } + if (initializeBuffer->skipSamples > 0) { + instance.gaplessSkipSamples = initializeBuffer->skipSamples; + } + ORBIS_LOG_TODO("SIDEBAND_GAPLESS_DECODE", + instance.gaplessSkipSamples, + instance.gaplessTotalSamples); + } jobPtr += sizeof(BatchJobControlBufferRa); break; } @@ -288,9 +323,9 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, case Opcode::JobBufferOutputRa: { BatchJobOutputBufferRa *job = (BatchJobOutputBufferRa *)jobPtr; ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobOutputBufferRa", - job->opcode, job->szOutputSize, job->pOutput); + job->opcode, job->outputSize, job->pOutput); runJob.pOutput = job->pOutput; - runJob.outputSize = job->szOutputSize; + runJob.outputSize = job->outputSize; jobPtr += sizeof(BatchJobOutputBufferRa); break; } @@ -320,19 +355,19 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, stream->outputSize = runJob.outputSize; } } else if (!runJob.control) { - orbis::uint32_t outputChannels = instance.outputChannels == AJM_DEFAULT - ? 2 - : instance.outputChannels; + orbis::uint32_t maxChannels = + instance.maxChannels == AJM_CHANNEL_DEFAULT ? 2 + : instance.maxChannels; 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; + orbis::uint32_t inputReaded = 0; + orbis::uint32_t outputWritten = 0; + orbis::uint32_t framesProcessed = 0; + orbis::uint32_t channels = 0; + orbis::uint32_t sampleRate = 0; if (runJob.inputSize != 0 && runJob.outputSize != 0) { while (inputReaded < runJob.inputSize && outputWritten < runJob.outputSize) { @@ -342,9 +377,10 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, break; } if (instance.codec == AJM_CODEC_At9) { - outputChannels = instance.outputChannels == AJM_DEFAULT - ? instance.at9.inputChannels - : instance.outputChannels; + orbis::uint32_t outputChannels = + instance.at9.inputChannels > maxChannels + ? maxChannels + : instance.at9.inputChannels; orbis::int32_t outputBufferSize = av_samples_get_buffer_size( nullptr, outputChannels, instance.at9.frameSamples, ajmToAvFormat(instance.resampler ? AJM_FORMAT_S16 @@ -363,6 +399,40 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, ORBIS_LOG_FATAL("Could not decode frame", err); 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; + } + // TODO: possible memory leak because "genius" code to avoiding memory + // copying + if (instance.gaplessSkipSamples > 0 || + instance.gaplessTotalSamples > 0) { + if (instance.gaplessSkipSamples > + instance.gaplessTotalSkippedSamples) { + instance.gaplessTotalSkippedSamples += + instance.at9.frameSamples; + inputReaded += instance.at9.estimatedSizeUsed; + ORBIS_LOG_TODO("skip frame", + instance.gaplessTotalSkippedSamples); + break; + } else if (instance.processedSamples > + instance.gaplessTotalSamples) { + instance.gaplessTotalSkippedSamples += + instance.at9.frameSamples; + inputReaded += instance.at9.estimatedSizeUsed; + ORBIS_LOG_TODO( + "skip output", instance.gaplessTotalSkippedSamples, + instance.processedSamples, instance.gaplessTotalSamples); + break; + } + } if (instance.resampler) { auto outputBuffer = reinterpret_cast( runJob.pOutput + outputWritten); @@ -377,21 +447,11 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, } 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); + outputWritten += std::max((orbis::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, @@ -414,8 +474,10 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, // realInputSize}); AVPacket *pkt = av_packet_alloc(); + rx::atScopeExit _free_pkt([&] { av_packet_free(&pkt); }); AVFrame *frame = av_frame_alloc(); - pkt->data = (uint8_t *)(runJob.pInput + inputReaded); + rx::atScopeExit _free_frame([&] { av_frame_free(&frame); }); + pkt->data = (orbis::uint8_t *)(runJob.pInput + inputReaded); pkt->size = realInputSize; int ret = avcodec_send_packet(instance.codecCtx, pkt); if (ret < 0) { @@ -428,12 +490,38 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, std::abort(); } + if (instance.gaplessSkipSamples > 0 || + instance.gaplessTotalSamples > 0) { + if (instance.gaplessSkipSamples > + instance.gaplessTotalSkippedSamples) { + instance.gaplessTotalSkippedSamples += frame->nb_samples; + inputReaded += realInputSize; + ORBIS_LOG_TODO("skip frame", + instance.gaplessTotalSkippedSamples); + break; + } else if (instance.processedSamples > + instance.gaplessTotalSamples) { + instance.gaplessTotalSkippedSamples += frame->nb_samples; + inputReaded += realInputSize; + ORBIS_LOG_TODO( + "skip output", instance.gaplessTotalSkippedSamples, + instance.processedSamples, instance.gaplessTotalSamples); + break; + } + } + auto resampler = swr_alloc(); + rx::atScopeExit _free_resampler([&] { swr_free(&resampler); }); if (!resampler) { ORBIS_LOG_FATAL("Could not allocate resampler context"); std::abort(); } + orbis::uint32_t outputChannels = + (orbis::uint32_t)frame->ch_layout.nb_channels > maxChannels + ? maxChannels + : frame->ch_layout.nb_channels; + AVChannelLayout inputChLayout; av_channel_layout_default(&inputChLayout, frame->ch_layout.nb_channels); @@ -458,6 +546,8 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, } uint8_t *outputBuffer = NULL; + rx::atScopeExit _free_outputBuffer( + [&] { av_freep(&outputBuffer); }); int outputBufferSize = av_samples_alloc( &outputBuffer, NULL, frame->ch_layout.nb_channels, frame->nb_samples, ajmToAvFormat(instance.outputFormat), 0); @@ -467,7 +557,9 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, } ORBIS_LOG_TODO("output buffer info", frame->ch_layout.nb_channels, frame->nb_samples, (int32_t)instance.outputFormat, - outputBufferSize); + outputBufferSize, + instance.gaplessTotalSkippedSamples, + instance.processedSamples); if (outputWritten + outputBufferSize > runJob.outputSize) { ORBIS_LOG_TODO("overwriting", outputWritten, outputBufferSize, @@ -490,14 +582,17 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, sampleRate = frame->sample_rate; inputReaded += realInputSize; outputWritten += outputBufferSize; + instance.processedSamples += frame->nb_samples; framesProcessed += 1; - av_freep(&outputBuffer); - swr_free(&resampler); - av_frame_free(&frame); - av_packet_free(&pkt); + // 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(); + rx::atScopeExit _free_pkt([&] { av_packet_free(&pkt); }); AVFrame *frame = av_frame_alloc(); + rx::atScopeExit _free_frame([&] { av_frame_free(&frame); }); pkt->data = (uint8_t *)runJob.pInput + inputReaded; pkt->size = runJob.inputSize; @@ -510,16 +605,37 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, ->cb.decode(instance.codecCtx, frame, &gotFrame, pkt); orbis::uint32_t outputChannels = - instance.outputChannels == AJM_DEFAULT - ? frame->ch_layout.nb_channels - : instance.outputChannels; + (orbis::uint32_t)frame->ch_layout.nb_channels > maxChannels + ? maxChannels + : frame->ch_layout.nb_channels; ORBIS_LOG_TODO("aac decode", len, gotFrame, frame->ch_layout.nb_channels, frame->sample_rate, instance.aac.sampleRate, outputChannels, - (orbis::uint32_t)instance.outputChannels); + (orbis::uint32_t)instance.maxChannels); + + if (instance.gaplessSkipSamples > 0 || + instance.gaplessTotalSamples > 0) { + if (instance.gaplessSkipSamples > + instance.gaplessTotalSkippedSamples) { + instance.gaplessTotalSkippedSamples += frame->nb_samples; + inputReaded += len; + ORBIS_LOG_TODO("skip frame", + instance.gaplessTotalSkippedSamples); + break; + } else if (instance.processedSamples > + instance.gaplessTotalSamples) { + instance.gaplessTotalSkippedSamples += frame->nb_samples; + inputReaded += len; + ORBIS_LOG_TODO( + "skip output", instance.gaplessTotalSkippedSamples, + instance.processedSamples, instance.gaplessTotalSamples); + break; + } + } auto resampler = swr_alloc(); + rx::atScopeExit _free_resampler([&] { swr_free(&resampler); }); if (!resampler) { ORBIS_LOG_FATAL("Could not allocate resampler context"); std::abort(); @@ -549,6 +665,8 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, } uint8_t *outputBuffer = NULL; + rx::atScopeExit _free_outputBuffer( + [&] { av_freep(&outputBuffer); }); int outputBufferSize = av_samples_alloc( &outputBuffer, NULL, outputChannels, frame->nb_samples, ajmToAvFormat(instance.outputFormat), 0); @@ -572,9 +690,10 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, inputReaded += len; outputWritten += outputBufferSize; framesProcessed += 1; - av_frame_free(&frame); - av_packet_free(&pkt); - swr_free(&resampler); + instance.processedSamples += frame->nb_samples; + // av_frame_free(&frame); + // av_packet_free(&pkt); + // swr_free(&resampler); } if (!(runJob.flags & RUN_MULTIPLE_FRAMES)) { break; @@ -586,11 +705,12 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, if (runJob.flags & SIDEBAND_STREAM) { ORBIS_LOG_TODO("SIDEBAND_STREAM", currentSize, inputReaded, - outputWritten); + outputWritten, instance.processedSamples); AJMSidebandStream *stream = reinterpret_cast( runJob.pSideband + currentSize); stream->inputSize = inputReaded; stream->outputSize = outputWritten; + stream->decodedSamples = instance.processedSamples; currentSize += sizeof(AJMSidebandStream); } @@ -604,6 +724,17 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, 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.gaplessSkipSamples; + gapless->totalSamples = instance.gaplessTotalSamples; + gapless->totalSkippedSamples = instance.gaplessTotalSkippedSamples; + currentSize += sizeof(AJMSidebandGaplessDecode); + } + if (runJob.flags & RUN_GET_CODEC_INFO) { ORBIS_LOG_TODO("RUN_GET_CODEC_INFO"); if (instance.codec == AJM_CODEC_At9) { diff --git a/rpcsx/iodev/ajm.hpp b/rpcsx/iodev/ajm.hpp index b5f9cbba5..0f5a3ac1f 100644 --- a/rpcsx/iodev/ajm.hpp +++ b/rpcsx/iodev/ajm.hpp @@ -1,3 +1,5 @@ +#pragma once + #include "orbis-config.hpp" #include "orbis/utils/Logs.hpp" #include @@ -16,14 +18,14 @@ enum class Opcode : std::uint8_t { JobBufferSidebandRa = 18, }; -typedef struct InstructionHeader { +struct InstructionHeader { orbis::uint32_t id; orbis::uint32_t len; -} InstructionHeader; +}; static_assert(sizeof(InstructionHeader) == 0x8); -typedef struct OpcodeHeader { +struct OpcodeHeader { orbis::uint32_t opcode; Opcode getOpcode() const { @@ -35,16 +37,16 @@ typedef struct OpcodeHeader { return static_cast(opcode & 0x1f); } -} OpcodeHeader; +}; -typedef struct ReturnAddress { +struct ReturnAddress { orbis::uint32_t opcode; orbis::uint32_t unk; // 0, padding? orbis::ptr returnAddress; -} ReturnAddress; +}; static_assert(sizeof(ReturnAddress) == 0x10); -typedef struct BatchJobControlBufferRa { +struct BatchJobControlBufferRa { orbis::uint32_t opcode; orbis::uint32_t sidebandInputSize; std::byte* pSidebandInput; @@ -53,38 +55,42 @@ typedef struct BatchJobControlBufferRa { orbis::uint32_t commandId; orbis::uint32_t sidebandOutputSize; std::byte* pSidebandOutput; -} BatchJobControlBufferRa; + + std::uint64_t getFlags() { + return ((uint64_t)flagsHi << 0x1a) | flagsLo; + } +}; static_assert(sizeof(BatchJobControlBufferRa) == 0x28); -typedef struct BatchJobInputBufferRa { +struct BatchJobInputBufferRa { orbis::uint32_t opcode; orbis::uint32_t szInputSize; std::byte* pInput; -} BatchJobInputBufferRa; +}; static_assert(sizeof(BatchJobInputBufferRa) == 0x10); -typedef struct BatchJobFlagsRa { +struct BatchJobFlagsRa { orbis::uint32_t flagsHi; orbis::uint32_t flagsLo; -} BatchJobFlagsRa; +}; static_assert(sizeof(BatchJobFlagsRa) == 0x8); -typedef struct BatchJobOutputBufferRa { +struct BatchJobOutputBufferRa { orbis::uint32_t opcode; - orbis::uint32_t szOutputSize; + orbis::uint32_t outputSize; std::byte* pOutput; -} BatchJobOutputBufferRa; +}; static_assert(sizeof(BatchJobOutputBufferRa) == 0x10); -typedef struct BatchJobSidebandBufferRa { +struct BatchJobSidebandBufferRa { orbis::uint32_t opcode; orbis::uint32_t sidebandSize; std::byte* pSideband; -} BatchJobSidebandBufferRa; +}; static_assert(sizeof(BatchJobSidebandBufferRa) == 0x10); -typedef struct RunJob { +struct RunJob { orbis::uint64_t flags; orbis::uint32_t inputSize; std::byte* pInput; @@ -93,7 +99,7 @@ typedef struct RunJob { 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 @@ -213,7 +219,7 @@ enum AJMCodecs : orbis::uint32_t { }; enum AJMChannels : orbis::uint32_t { - AJM_DEFAULT = 0, + AJM_CHANNEL_DEFAULT = 0, AJM_CHANNEL_1 = 1, AJM_CHANNEL_2 = 2, AJM_CHANNEL_3 = 3, @@ -229,7 +235,7 @@ enum AJMFormat : orbis::uint32_t { AJM_FORMAT_FLOAT = 2 }; -typedef struct At9Instance { +struct At9Instance { orbis::ptr handle; orbis::uint32_t inputChannels; orbis::uint32_t framesInSuperframe; @@ -239,57 +245,69 @@ typedef struct At9Instance { orbis::uint32_t superFrameSize; orbis::uint32_t estimatedSizeUsed; orbis::uint32_t sampleRate; -} At9Instance; +}; -typedef struct AACInstance { +struct AACInstance { AACHeaderType headerType; orbis::uint32_t sampleRate; -} AACInstance; +}; -typedef struct Instance { +struct Instance { AJMCodecs codec; - AJMChannels outputChannels; + AJMChannels maxChannels; AJMFormat outputFormat; At9Instance at9; AACInstance aac; AVCodecContext *codecCtx; SwrContext *resampler; orbis::uint32_t lastBatchId; -} Instance; + // TODO: use AJMSidebandGaplessDecode for these variables + orbis::uint32_t gaplessTotalSamples; + orbis::uint16_t gaplessSkipSamples; + orbis::uint16_t gaplessTotalSkippedSamples; + orbis::uint32_t processedSamples; +}; -typedef struct AJMSidebandResult { +struct AJMSidebandResult { orbis::int32_t result; orbis::int32_t codecResult; -} AJMSidebandResult; +}; -typedef struct AJMSidebandStream { +struct AJMSidebandStream { orbis::int32_t inputSize; orbis::int32_t outputSize; - orbis::uint64_t unk0; -} AJMSidebandStream; + orbis::uint64_t decodedSamples; +}; -typedef struct AJMSidebandMultipleFrames { +struct AJMSidebandMultipleFrames { orbis::uint32_t framesProcessed; orbis::uint32_t unk0; -} AJMSidebandMultipleFrames; +}; -typedef struct AJMSidebandFormat { +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 { + +struct AJMSidebandGaplessDecode { + orbis::uint32_t totalSamples; + orbis::uint16_t skipSamples; + orbis::uint16_t totalSkippedSamples; +}; + +struct AJMAt9CodecInfoSideband { orbis::uint32_t superFrameSize; orbis::uint32_t framesInSuperFrame; orbis::uint32_t unk0; orbis::uint32_t frameSamples; -} AJMAt9CodecInfoSideband; +}; -typedef struct AJMMP3CodecInfoSideband { +struct AJMMP3CodecInfoSideband { orbis::uint32_t header; orbis::uint8_t unk0; orbis::uint8_t unk1; @@ -300,15 +318,16 @@ typedef struct AJMMP3CodecInfoSideband { orbis::uint16_t unk6; orbis::uint16_t unk7; orbis::uint16_t unk8; -} AJMMP3CodecInfoSideband; +}; -typedef struct AJMAACCodecInfoSideband { +struct AJMAACCodecInfoSideband { orbis::uint32_t heaac; orbis::uint32_t unk0; -} AJMAACCodecInfoSideband; +}; enum ControlFlags { CONTROL_INITIALIZE = 0x4000, + CONTROL_RESET = 0x2000, }; enum RunFlags { @@ -318,5 +337,6 @@ enum RunFlags { enum SidebandFlags { SIDEBAND_STREAM = 0x800000000000, - SIDEBAND_FORMAT = 0x400000000000 + SIDEBAND_FORMAT = 0x400000000000, + SIDEBAND_GAPLESS_DECODE = 0x200000000000, }; \ No newline at end of file From fc8ea00e8f696ebb16d378fe37fa49691ea4dba6 Mon Sep 17 00:00:00 2001 From: Nikita Savyolov Date: Wed, 23 Oct 2024 20:33:58 +0300 Subject: [PATCH 3/5] rpcsx: ajm at9 fixes for one game --- rpcsx/iodev/ajm.cpp | 39 ++++++++++++++++++++++++++++++--------- rpcsx/iodev/ajm.hpp | 18 +++++++++--------- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/rpcsx/iodev/ajm.cpp b/rpcsx/iodev/ajm.cpp index 71f6147ff..3d1aa3f13 100644 --- a/rpcsx/iodev/ajm.cpp +++ b/rpcsx/iodev/ajm.cpp @@ -158,6 +158,7 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, args->pBatch, args->batchSize, args->priority, args->batchError, args->batchId); ajm::batchId += 1; + thread->where(); auto ptr = args->pBatch; auto endPtr = args->pBatch + args->batchSize; @@ -169,7 +170,8 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, auto endJobPtr = ptr + header->len; // TODO: handle unimplemented codecs, so auto create instance for now auto &instance = ajm::instanceMap[instanceId]; - RunJob runJob; + ORBIS_LOG_TODO("instance info", instanceId); + RunJob runJob{}; while (jobPtr < endJobPtr) { auto typed = (OpcodeHeader *)jobPtr; switch (typed->getOpcode()) { @@ -208,6 +210,16 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, }; InitalizeBuffer *initializeBuffer = (InitalizeBuffer *)ctrl->pSidebandInput; + 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, reinterpret_cast(&initializeBuffer->configData)); @@ -269,6 +281,10 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, std::abort(); } } + ORBIS_LOG_TODO("CONTROL_INITIALIZE", pCodecInfo.channels, + pCodecInfo.samplingRate, pCodecInfo.frameSamples, + pCodecInfo.superframeSize, maxChannels, + outputChannels, initializeBuffer->configData, &initializeBuffer->configData); } else if (instance.codec == AJM_CODEC_AAC) { struct InitalizeBuffer { orbis::uint32_t headerIndex; @@ -309,6 +325,7 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, job->opcode, job->szInputSize, job->pInput); runJob.pInput = job->pInput; runJob.inputSize = job->szInputSize; + // rx::hexdump({(std::byte*) job->pInput, job->szInputSize}); jobPtr += sizeof(BatchJobInputBufferRa); break; } @@ -410,8 +427,11 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, instance.at9.superFrameDataIdx = 0; instance.at9.superFrameDataLeft = instance.at9.superFrameSize; } - // TODO: possible memory leak because "genius" code to avoiding memory - // copying + + ORBIS_LOG_TODO("used size", bytesUsed, instance.at9.estimatedSizeUsed); + // TODO: possible memory leak because "genius" code to avoiding + // memory copying + framesProcessed += 1; if (instance.gaplessSkipSamples > 0 || instance.gaplessTotalSamples > 0) { if (instance.gaplessSkipSamples > @@ -421,7 +441,7 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, inputReaded += instance.at9.estimatedSizeUsed; ORBIS_LOG_TODO("skip frame", instance.gaplessTotalSkippedSamples); - break; + continue; } else if (instance.processedSamples > instance.gaplessTotalSamples) { instance.gaplessTotalSkippedSamples += @@ -430,7 +450,7 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, ORBIS_LOG_TODO( "skip output", instance.gaplessTotalSkippedSamples, instance.processedSamples, instance.gaplessTotalSamples); - break; + continue; } } if (instance.resampler) { @@ -450,9 +470,10 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, channels = instance.at9.inputChannels; sampleRate = instance.at9.sampleRate; inputReaded += instance.at9.estimatedSizeUsed; - outputWritten += std::max((orbis::uint32_t)outputBufferSize, - runJob.outputSize); - framesProcessed += 1; + // outputWritten += std::max((orbis::uint32_t)outputBufferSize, + // runJob.outputSize); + outputWritten += outputBufferSize; + instance.processedSamples += instance.at9.frameSamples; } else if (instance.codec == AJM_CODEC_MP3) { ORBIS_LOG_FATAL("Pre get mp3 data size info", runJob.inputSize, runJob.outputSize, runJob.sidebandSize, @@ -770,7 +791,6 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, } } } - } else if (request == 0xc0288908) { struct Args { orbis::uint32_t unk0; @@ -783,6 +803,7 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, 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(); diff --git a/rpcsx/iodev/ajm.hpp b/rpcsx/iodev/ajm.hpp index 0f5a3ac1f..fd3133e90 100644 --- a/rpcsx/iodev/ajm.hpp +++ b/rpcsx/iodev/ajm.hpp @@ -236,15 +236,15 @@ enum AJMFormat : orbis::uint32_t { }; 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; + 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{}; }; struct AACInstance { From c2fa3895a18b1c87e45d10bb8fb2aeeb51310389 Mon Sep 17 00:00:00 2001 From: Nikita Savyolov Date: Thu, 24 Oct 2024 22:49:51 +0300 Subject: [PATCH 4/5] rpcsx: free resampler in at9 initialize --- rpcsx/iodev/ajm.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/rpcsx/iodev/ajm.cpp b/rpcsx/iodev/ajm.cpp index 3d1aa3f13..1fe621772 100644 --- a/rpcsx/iodev/ajm.cpp +++ b/rpcsx/iodev/ajm.cpp @@ -248,6 +248,10 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, instance.at9.inputChannels > maxChannels ? maxChannels : instance.at9.inputChannels; + if (instance.resampler) { + swr_free(&instance.resampler); + instance.resampler = NULL; + } if (instance.at9.inputChannels != outputChannels || instance.outputFormat != AJM_FORMAT_S16) { instance.resampler = swr_alloc(); @@ -284,7 +288,8 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, ORBIS_LOG_TODO("CONTROL_INITIALIZE", pCodecInfo.channels, pCodecInfo.samplingRate, pCodecInfo.frameSamples, pCodecInfo.superframeSize, maxChannels, - outputChannels, initializeBuffer->configData, &initializeBuffer->configData); + outputChannels, initializeBuffer->configData, + &initializeBuffer->configData); } else if (instance.codec == AJM_CODEC_AAC) { struct InitalizeBuffer { orbis::uint32_t headerIndex; @@ -428,7 +433,8 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, instance.at9.superFrameDataLeft = instance.at9.superFrameSize; } - ORBIS_LOG_TODO("used size", bytesUsed, instance.at9.estimatedSizeUsed); + ORBIS_LOG_TODO("used size", bytesUsed, + instance.at9.estimatedSizeUsed); // TODO: possible memory leak because "genius" code to avoiding // memory copying framesProcessed += 1; @@ -462,7 +468,7 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, instance.at9.frameSamples, &tempBuffer, instance.at9.frameSamples); if (nb_samples < 0) { - ORBIS_LOG_FATAL("Error while resampling"); + ORBIS_LOG_FATAL("Error while resampling", nb_samples); std::abort(); } av_freep(&tempBuffer); From e45eabc7d85e018f8889ef936e44c7ab4c13b561 Mon Sep 17 00:00:00 2001 From: Nikita Savyolov Date: Tue, 29 Oct 2024 19:44:59 +0300 Subject: [PATCH 5/5] rpcsx: simplify and correct reset for ajm at9 --- rpcsx/iodev/ajm.cpp | 693 +++++++++++++++++--------------------------- rpcsx/iodev/ajm.hpp | 75 +++-- 2 files changed, 298 insertions(+), 470 deletions(-) diff --git a/rpcsx/iodev/ajm.cpp b/rpcsx/iodev/ajm.cpp index 1fe621772..2ed668349 100644 --- a/rpcsx/iodev/ajm.cpp +++ b/rpcsx/iodev/ajm.cpp @@ -7,7 +7,9 @@ #include "orbis/thread/Thread.hpp" #include "orbis/utils/Logs.hpp" #include +#include #include +#include #include #include extern "C" { @@ -24,15 +26,25 @@ extern "C" { struct AjmFile : orbis::File {}; -namespace ajm { -orbis::uint32_t batchId = 1; +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 at9InstanceId = 0; -orbis::uint32_t mp3InstanceId = 0; -orbis::uint32_t aacInstanceId = 0; -orbis::uint32_t unimplementedInstanceId = 0; -orbis::kmap instanceMap; -} // namespace ajm + 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) { @@ -47,9 +59,49 @@ AVSampleFormat ajmToAvFormat(AJMFormat ajmFormat) { } } +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 @@ -67,14 +119,16 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, orbis::uint32_t instanceId; }; auto args = reinterpret_cast(argp); - auto it = ajm::instanceMap.find(args->instanceId); - if (it != ajm::instanceMap.end()) { - auto &instance = ajm::instanceMap[args->instanceId]; + 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); } - ajm::instanceMap.erase(args->instanceId); + device->instanceMap.erase(it); } args->result = 0; } @@ -98,11 +152,11 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, if (codecId >= 0 && codecId <= 2) { args->result = 0; if (codecId == AJM_CODEC_At9) { - args->instanceId = codecOffset + ajm::at9InstanceId++; + args->instanceId = codecOffset + device->at9InstanceId++; } else if (codecId == AJM_CODEC_MP3) { - args->instanceId = codecOffset + ajm::mp3InstanceId++; + args->instanceId = codecOffset + device->mp3InstanceId++; } else if (codecId == AJM_CODEC_AAC) { - args->instanceId = codecOffset + ajm::aacInstanceId++; + args->instanceId = codecOffset + device->aacInstanceId++; } Instance instance; instance.codec = codecId; @@ -111,6 +165,15 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, 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( @@ -132,12 +195,12 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, instance.codecCtx = codecCtx; } - ajm::instanceMap.insert({ + device->instanceMap.insert({ args->instanceId, instance, }); } else { - args->instanceId = codecOffset + ajm::unimplementedInstanceId++; + args->instanceId = codecOffset + device->unimplementedInstanceId++; } ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0, args->flags, args->codec, args->instanceId); @@ -153,12 +216,12 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, }; auto args = reinterpret_cast(argp); args->result = 0; - args->batchId = ajm::batchId; - ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0, - args->pBatch, args->batchSize, args->priority, - args->batchError, args->batchId); - ajm::batchId += 1; - thread->where(); + 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; @@ -169,16 +232,17 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, auto jobPtr = ptr + sizeof(InstructionHeader); auto endJobPtr = ptr + header->len; // TODO: handle unimplemented codecs, so auto create instance for now - auto &instance = ajm::instanceMap[instanceId]; - ORBIS_LOG_TODO("instance info", instanceId); + 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); + // ReturnAddress *ra = (ReturnAddress *)jobPtr; + // ORBIS_LOG_ERROR(__FUNCTION__, request, "return address", + // ra->opcode, + // ra->unk, ra->returnAddress); jobPtr += sizeof(ReturnAddress); break; } @@ -193,13 +257,10 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, ctrl->commandId, ctrl->flagsHi, ctrl->flagsLo, ctrl->sidebandInputSize, ctrl->sidebandOutputSize); if (ctrl->getFlags() & CONTROL_RESET) { - // instance.outputChannels = AJMChannels(0); - // instance.outputFormat = AJMFormat(0); - instance.gaplessSkipSamples = 0; - instance.gaplessTotalSamples = 0; - instance.gaplessTotalSkippedSamples = 0; - instance.processedSamples = 0; - ORBIS_LOG_TODO("CONTROL_RESET"); + reset(&instance); + if (instance.codec == AJM_CODEC_At9) { + resetAt9(&instance); + } } if (ctrl->getFlags() & CONTROL_INITIALIZE) { @@ -210,35 +271,9 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, }; InitalizeBuffer *initializeBuffer = (InitalizeBuffer *)ctrl->pSidebandInput; - 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, - 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; + instance.at9.configData = initializeBuffer->configData; + reset(&instance); + resetAt9(&instance); orbis::uint32_t maxChannels = instance.maxChannels == AJM_CHANNEL_DEFAULT @@ -248,48 +283,12 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, instance.at9.inputChannels > maxChannels ? maxChannels : instance.at9.inputChannels; - if (instance.resampler) { - swr_free(&instance.resampler); - instance.resampler = NULL; - } - 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(); - } - } - ORBIS_LOG_TODO("CONTROL_INITIALIZE", pCodecInfo.channels, - pCodecInfo.samplingRate, pCodecInfo.frameSamples, - pCodecInfo.superframeSize, maxChannels, + // 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, - &initializeBuffer->configData); + (orbis::uint32_t)instance.outputFormat); } else if (instance.codec == AJM_CODEC_AAC) { struct InitalizeBuffer { orbis::uint32_t headerIndex; @@ -312,40 +311,44 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, InitalizeBuffer *initializeBuffer = (InitalizeBuffer *)ctrl->pSidebandInput; if (initializeBuffer->totalSamples > 0) { - instance.gaplessTotalSamples = initializeBuffer->totalSamples; + instance.gapless.totalSamples = initializeBuffer->totalSamples; } if (initializeBuffer->skipSamples > 0) { - instance.gaplessSkipSamples = initializeBuffer->skipSamples; + instance.gapless.skipSamples = initializeBuffer->skipSamples; } ORBIS_LOG_TODO("SIDEBAND_GAPLESS_DECODE", - instance.gaplessSkipSamples, - instance.gaplessTotalSamples); + 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); - runJob.pInput = job->pInput; - runJob.inputSize = job->szInputSize; + // 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 = ((uint64_t)job->flagsHi << 0x1a) | job->flagsLo; + // 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); + // ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobOutputBufferRa", + // job->opcode, job->outputSize, job->pOutput); runJob.pOutput = job->pOutput; runJob.outputSize = job->outputSize; jobPtr += sizeof(BatchJobOutputBufferRa); @@ -353,8 +356,8 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, } case Opcode::JobBufferSidebandRa: { BatchJobSidebandBufferRa *job = (BatchJobSidebandBufferRa *)jobPtr; - ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobSidebandBufferRa", - job->opcode, job->sidebandSize, job->pSideband); + // ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobSidebandBufferRa", + // job->opcode, job->sidebandSize, job->pSideband); runJob.pSideband = job->pSideband; runJob.sidebandSize = job->sidebandSize; jobPtr += sizeof(BatchJobSidebandBufferRa); @@ -373,13 +376,14 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, if (runJob.flags & SIDEBAND_STREAM) { AJMSidebandStream *stream = reinterpret_cast(runJob.pSideband + 8); - stream->inputSize = runJob.inputSize; + stream->inputSize = device->inputSize; stream->outputSize = runJob.outputSize; } } else if (!runJob.control) { - orbis::uint32_t maxChannels = - instance.maxChannels == AJM_CHANNEL_DEFAULT ? 2 - : instance.maxChannels; + // orbis::uint32_t maxChannels = + // instance.maxChannels == AJM_CHANNEL_DEFAULT ? 2 + // : + // instance.maxChannels; AJMSidebandResult *result = reinterpret_cast(runJob.pSideband); result->result = 0; @@ -388,37 +392,41 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, orbis::uint32_t inputReaded = 0; orbis::uint32_t outputWritten = 0; orbis::uint32_t framesProcessed = 0; - orbis::uint32_t channels = 0; - orbis::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) { + 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; } - if (instance.codec == AJM_CODEC_At9) { - orbis::uint32_t outputChannels = - instance.at9.inputChannels > maxChannels - ? maxChannels - : instance.at9.inputChannels; - 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); + 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; - int err = - Atrac9Decode(instance.at9.handle, runJob.pInput + inputReaded, - tempBuffer, kAtrac9FormatS16, &bytesUsed); + 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) { - ORBIS_LOG_FATAL("Could not decode frame", err); + 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 = @@ -432,80 +440,27 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, instance.at9.superFrameDataIdx = 0; instance.at9.superFrameDataLeft = instance.at9.superFrameSize; } - - ORBIS_LOG_TODO("used size", bytesUsed, - instance.at9.estimatedSizeUsed); - // TODO: possible memory leak because "genius" code to avoiding - // memory copying - framesProcessed += 1; - if (instance.gaplessSkipSamples > 0 || - instance.gaplessTotalSamples > 0) { - if (instance.gaplessSkipSamples > - instance.gaplessTotalSkippedSamples) { - instance.gaplessTotalSkippedSamples += - instance.at9.frameSamples; - inputReaded += instance.at9.estimatedSizeUsed; - ORBIS_LOG_TODO("skip frame", - instance.gaplessTotalSkippedSamples); - continue; - } else if (instance.processedSamples > - instance.gaplessTotalSamples) { - instance.gaplessTotalSkippedSamples += - instance.at9.frameSamples; - inputReaded += instance.at9.estimatedSizeUsed; - ORBIS_LOG_TODO( - "skip output", instance.gaplessTotalSkippedSamples, - instance.processedSamples, instance.gaplessTotalSamples); - continue; - } - } - 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", nb_samples); - std::abort(); - } - av_freep(&tempBuffer); - } - channels = instance.at9.inputChannels; - sampleRate = instance.at9.sampleRate; - inputReaded += instance.at9.estimatedSizeUsed; - // outputWritten += std::max((orbis::uint32_t)outputBufferSize, - // runJob.outputSize); - outputWritten += outputBufferSize; - instance.processedSamples += instance.at9.frameSamples; + 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) { - 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; + auto frameSize = get_mp3_data_size( + (orbis::uint8_t *)(&device->inputBuffer[inputReaded])); + if (frameSize == 0) { + frameSize = device->inputSize; } else { - realInputSize = std::min(realInputSize, runJob.inputSize); + frameSize = std::min(frameSize, device->inputSize); } - - if (inputReaded + realInputSize > runJob.inputSize) { - break; - } - - // rx::hexdump( - // {(std::byte *)(runJob.pInput + inputReaded), - // realInputSize}); - 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); }); - pkt->data = (orbis::uint8_t *)(runJob.pInput + inputReaded); - pkt->size = realInputSize; + 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); @@ -516,49 +471,62 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, 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; - if (instance.gaplessSkipSamples > 0 || - instance.gaplessTotalSamples > 0) { - if (instance.gaplessSkipSamples > - instance.gaplessTotalSkippedSamples) { - instance.gaplessTotalSkippedSamples += frame->nb_samples; - inputReaded += realInputSize; - ORBIS_LOG_TODO("skip frame", - instance.gaplessTotalSkippedSamples); - break; - } else if (instance.processedSamples > - instance.gaplessTotalSamples) { - instance.gaplessTotalSkippedSamples += frame->nb_samples; - inputReaded += realInputSize; - ORBIS_LOG_TODO( - "skip output", instance.gaplessTotalSkippedSamples, - instance.processedSamples, instance.gaplessTotalSamples); - break; - } - } - - auto resampler = swr_alloc(); - rx::atScopeExit _free_resampler([&] { swr_free(&resampler); }); - if (!resampler) { - ORBIS_LOG_FATAL("Could not allocate resampler context"); + // 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; - orbis::uint32_t outputChannels = - (orbis::uint32_t)frame->ch_layout.nb_channels > maxChannels - ? maxChannels - : frame->ch_layout.nb_channels; - - AVChannelLayout inputChLayout; - av_channel_layout_default(&inputChLayout, + 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); - - 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_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, @@ -571,168 +539,29 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, ORBIS_LOG_FATAL("Failed to initialize the resampling context"); std::abort(); } - - uint8_t *outputBuffer = NULL; - rx::atScopeExit _free_outputBuffer( - [&] { av_freep(&outputBuffer); }); - 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, - instance.gaplessTotalSkippedSamples, - instance.processedSamples); - - if (outputWritten + outputBufferSize > runJob.outputSize) { - ORBIS_LOG_TODO("overwriting", outputWritten, outputBufferSize, - outputWritten + outputBufferSize, - runJob.outputSize); - break; - } - + orbis::uint8_t *outputBuffer = + reinterpret_cast(device->tempBuffer.data()); int nb_samples = swr_convert(resampler, &outputBuffer, frame->nb_samples, - (const uint8_t **)frame->data, 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, outputBuffer, - outputBufferSize); - channels = frame->ch_layout.nb_channels; - sampleRate = frame->sample_rate; - inputReaded += realInputSize; - outputWritten += outputBufferSize; - instance.processedSamples += frame->nb_samples; - 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(); - rx::atScopeExit _free_pkt([&] { av_packet_free(&pkt); }); - AVFrame *frame = av_frame_alloc(); - rx::atScopeExit _free_frame([&] { av_frame_free(&frame); }); - 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 = - (orbis::uint32_t)frame->ch_layout.nb_channels > maxChannels - ? maxChannels - : frame->ch_layout.nb_channels; - - ORBIS_LOG_TODO("aac decode", len, gotFrame, - frame->ch_layout.nb_channels, frame->sample_rate, - instance.aac.sampleRate, outputChannels, - (orbis::uint32_t)instance.maxChannels); - - if (instance.gaplessSkipSamples > 0 || - instance.gaplessTotalSamples > 0) { - if (instance.gaplessSkipSamples > - instance.gaplessTotalSkippedSamples) { - instance.gaplessTotalSkippedSamples += frame->nb_samples; - inputReaded += len; - ORBIS_LOG_TODO("skip frame", - instance.gaplessTotalSkippedSamples); - break; - } else if (instance.processedSamples > - instance.gaplessTotalSamples) { - instance.gaplessTotalSkippedSamples += frame->nb_samples; - inputReaded += len; - ORBIS_LOG_TODO( - "skip output", instance.gaplessTotalSkippedSamples, - instance.processedSamples, instance.gaplessTotalSamples); - break; - } - } - - auto resampler = swr_alloc(); - rx::atScopeExit _free_resampler([&] { swr_free(&resampler); }); - 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; - rx::atScopeExit _free_outputBuffer( - [&] { av_freep(&outputBuffer); }); - 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; - instance.processedSamples += frame->nb_samples; - // av_frame_free(&frame); - // av_packet_free(&pkt); - // swr_free(&resampler); } - if (!(runJob.flags & RUN_MULTIPLE_FRAMES)) { - break; - } - } + 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); + // ORBIS_LOG_TODO("SIDEBAND_STREAM", currentSize, inputReaded, + // outputWritten, instance.processedSamples); AJMSidebandStream *stream = reinterpret_cast( runJob.pSideband + currentSize); stream->inputSize = inputReaded; @@ -742,28 +571,29 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, } if (runJob.flags & SIDEBAND_FORMAT) { - ORBIS_LOG_TODO("SIDEBAND_FORMAT", currentSize); + // 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; + 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); + // ORBIS_LOG_TODO("SIDEBAND_GAPLESS_DECODE", currentSize); AJMSidebandGaplessDecode *gapless = reinterpret_cast(runJob.pSideband + currentSize); - gapless->skipSamples = instance.gaplessSkipSamples; - gapless->totalSamples = instance.gaplessTotalSamples; - gapless->totalSkippedSamples = instance.gaplessTotalSkippedSamples; + 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"); + // ORBIS_LOG_TODO("RUN_GET_CODEC_INFO"); if (instance.codec == AJM_CODEC_At9) { AJMAt9CodecInfoSideband *info = reinterpret_cast(runJob.pSideband + @@ -788,7 +618,7 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, } if (runJob.flags & RUN_MULTIPLE_FRAMES) { - ORBIS_LOG_TODO("RUN_MULTIPLE_FRAMES", currentSize); + // ORBIS_LOG_TODO("RUN_MULTIPLE_FRAMES", framesProcessed); AJMSidebandMultipleFrames *multipleFrames = reinterpret_cast(runJob.pSideband + currentSize); @@ -807,9 +637,9 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request, }; 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(); + // 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(); @@ -821,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 index fd3133e90..8ec3a7a65 100644 --- a/rpcsx/iodev/ajm.hpp +++ b/rpcsx/iodev/ajm.hpp @@ -1,7 +1,8 @@ #pragma once +#include "libatrac9/libatrac9.h" #include "orbis-config.hpp" -#include "orbis/utils/Logs.hpp" +// #include "orbis/utils/Logs.hpp" #include extern "C" { #include @@ -29,7 +30,7 @@ struct OpcodeHeader { orbis::uint32_t opcode; Opcode getOpcode() const { - ORBIS_LOG_ERROR(__FUNCTION__, opcode); + // ORBIS_LOG_ERROR(__FUNCTION__, opcode); if (auto loType = static_cast(opcode & 0xf); loType == Opcode::ReturnAddress || loType == Opcode::Flags) { return loType; @@ -49,23 +50,21 @@ static_assert(sizeof(ReturnAddress) == 0x10); struct BatchJobControlBufferRa { orbis::uint32_t opcode; orbis::uint32_t sidebandInputSize; - std::byte* pSidebandInput; + std::byte *pSidebandInput; orbis::uint32_t flagsHi; orbis::uint32_t flagsLo; orbis::uint32_t commandId; orbis::uint32_t sidebandOutputSize; - std::byte* pSidebandOutput; + std::byte *pSidebandOutput; - std::uint64_t getFlags() { - return ((uint64_t)flagsHi << 0x1a) | flagsLo; - } + 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; + std::byte *pInput; }; static_assert(sizeof(BatchJobInputBufferRa) == 0x10); @@ -79,25 +78,23 @@ static_assert(sizeof(BatchJobFlagsRa) == 0x8); struct BatchJobOutputBufferRa { orbis::uint32_t opcode; orbis::uint32_t outputSize; - std::byte* pOutput; + std::byte *pOutput; }; static_assert(sizeof(BatchJobOutputBufferRa) == 0x10); struct BatchJobSidebandBufferRa { orbis::uint32_t opcode; orbis::uint32_t sidebandSize; - std::byte* pSideband; + std::byte *pSideband; }; static_assert(sizeof(BatchJobSidebandBufferRa) == 0x10); struct RunJob { orbis::uint64_t flags; - orbis::uint32_t inputSize; - std::byte* pInput; orbis::uint32_t outputSize; - std::byte* pOutput; + std::byte *pOutput; orbis::uint32_t sidebandSize; - std::byte* pSideband; + std::byte *pSideband; bool control; }; @@ -198,10 +195,10 @@ uint32_t get_mp3_data_size(const uint8_t *data) { ((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)); + // 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); @@ -245,6 +242,8 @@ struct At9Instance { orbis::uint32_t superFrameSize{}; orbis::uint32_t estimatedSizeUsed{}; orbis::uint32_t sampleRate{}; + Atrac9Format outputFormat{}; + orbis::uint32_t configData; }; struct AACInstance { @@ -252,20 +251,10 @@ struct AACInstance { orbis::uint32_t sampleRate; }; -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 - orbis::uint32_t gaplessTotalSamples; - orbis::uint16_t gaplessSkipSamples; - orbis::uint16_t gaplessTotalSkippedSamples; - orbis::uint32_t processedSamples; +struct AJMSidebandGaplessDecode { + orbis::uint32_t totalSamples; + orbis::uint16_t skipSamples; + orbis::uint16_t totalSkippedSamples; }; struct AJMSidebandResult { @@ -293,13 +282,6 @@ struct AJMSidebandFormat { uint32_t unk1; }; - -struct AJMSidebandGaplessDecode { - orbis::uint32_t totalSamples; - orbis::uint16_t skipSamples; - orbis::uint16_t totalSkippedSamples; -}; - struct AJMAt9CodecInfoSideband { orbis::uint32_t superFrameSize; orbis::uint32_t framesInSuperFrame; @@ -325,6 +307,21 @@ struct AJMAACCodecInfoSideband { 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,