[WIP] ajm: async instances

This commit is contained in:
DH 2024-11-01 00:48:31 +03:00
parent 8440940945
commit ce8d4dcf16
8 changed files with 864 additions and 943 deletions

View file

@ -9,6 +9,8 @@ add_library(orbis::kernel::config ALIAS standalone-config)
add_executable(rpcsx
audio/AudioDevice.cpp
audio/AlsaDevice.cpp
audio/At9Codec.cpp
audio/FFmpegCodecs.cpp
iodev/ajm.cpp
iodev/blockpool.cpp

54
rpcsx/audio/At9Codec.cpp Normal file
View file

@ -0,0 +1,54 @@
#include "../iodev/ajm.hpp"
#include <cstddef>
extern "C" {
#include <libatrac9/decoder.h>
#include <libatrac9/libatrac9.h>
}
struct At9CodecInfoSideband {
orbis::uint32_t superFrameSize;
orbis::uint32_t framesInSuperFrame;
orbis::uint32_t unk0;
orbis::uint32_t frameSamples;
};
struct At9CodecInstance : ajm::CodecInstance {
std::vector<std::byte> inputBuffer;
std::vector<std::byte> outputBuffer;
orbis::ptr<void> handle{};
orbis::uint32_t inputChannels{};
orbis::uint32_t framesInSuperframe{};
orbis::uint32_t frameSamples{};
orbis::uint32_t superFrameDataLeft{};
orbis::uint32_t superFrameDataIdx{};
orbis::uint32_t superFrameSize{};
orbis::uint32_t estimatedSizeUsed{};
orbis::uint32_t sampleRate{};
Atrac9Format outputFormat{};
orbis::uint32_t configData{};
~At9CodecInstance() {
if (handle) {
Atrac9ReleaseHandle(handle);
}
}
std::uint64_t runJob(const ajm::Job &job) override {}
void reset() override {}
};
struct At9Codec : ajm::Codec {
orbis::ErrorCode createInstance(orbis::Ref<ajm::CodecInstance> *instance,
std::uint32_t unk0,
std::uint64_t flags) override {
auto at9 = orbis::Ref{orbis::knew<At9CodecInstance>()};
*instance = at9;
return {};
}
};
void createAt9Codec(AjmDevice *ajm) {
ajm->createCodec<At9Codec>(ajm::CodecId::At9);
}

3
rpcsx/audio/At9Codec.hpp Normal file
View file

@ -0,0 +1,3 @@
#pragma once
void createAt9Codec(class AjmDevice *ajm);

View file

@ -0,0 +1,216 @@
#include "../iodev/ajm.hpp"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavcodec/codec_internal.h>
#include <libavcodec/packet.h>
#include <libavformat/avformat.h>
#include <libavutil/mem.h>
#include <libavutil/opt.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
}
// Thanks to mystical SirNickity with 1 post
// https://hydrogenaud.io/index.php?topic=85125.msg747716#msg747716
inline constexpr uint8_t mpeg_versions[4] = {25, 0, 2, 1};
// Layers - use [layer]
inline constexpr uint8_t mpeg_layers[4] = {0, 3, 2, 1};
// Bitrates - use [version][layer][bitrate]
inline constexpr uint16_t mpeg_bitrates[4][4][16] = {
{
// Version 2.5
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,
0}, // Layer 3
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,
0}, // Layer 2
{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256,
0} // Layer 1
},
{
// Reserved
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Invalid
},
{
// Version 2
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,
0}, // Layer 3
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,
0}, // Layer 2
{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256,
0} // Layer 1
},
{
// Version 1
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
0}, // Layer 3
{0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384,
0}, // Layer 2
{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448,
0}, // Layer 1
}};
// Sample rates - use [version][srate]
inline constexpr uint16_t mpeg_srates[4][4] = {
{11025, 12000, 8000, 0}, // MPEG 2.5
{0, 0, 0, 0}, // Reserved
{22050, 24000, 16000, 0}, // MPEG 2
{44100, 48000, 32000, 0} // MPEG 1
};
// Samples per frame - use [version][layer]
inline constexpr uint16_t mpeg_frame_samples[4][4] = {
// Rsvd 3 2 1 < Layer v Version
{0, 576, 1152, 384}, // 2.5
{0, 0, 0, 0}, // Reserved
{0, 576, 1152, 384}, // 2
{0, 1152, 1152, 384} // 1
};
// Slot size (MPEG unit of measurement) - use [layer]
inline constexpr uint8_t mpeg_slot_size[4] = {0, 1, 1, 4}; // Rsvd, 3, 2, 1
constexpr uint32_t get_mp3_data_size(const uint8_t *data) {
// Quick validity check
if (((data[0] & 0xFF) != 0xFF) || ((data[1] & 0xE0) != 0xE0) // 3 sync bits
|| ((data[1] & 0x18) == 0x08) // Version rsvd
|| ((data[1] & 0x06) == 0x00) // Layer rsvd
|| ((data[2] & 0xF0) == 0xF0) // Bitrate rsvd
) {
return 0;
}
// Data to be extracted from the header
uint8_t ver = (data[1] & 0x18) >> 3; // Version index
uint8_t lyr = (data[1] & 0x06) >> 1; // Layer index
uint8_t pad = (data[2] & 0x02) >> 1; // Padding? 0/1
uint8_t brx = (data[2] & 0xf0) >> 4; // Bitrate index
uint8_t srx = (data[2] & 0x0c) >> 2; // SampRate index
// Lookup real values of these fields
uint32_t bitrate = mpeg_bitrates[ver][lyr][brx] * 1000;
uint32_t samprate = mpeg_srates[ver][srx];
uint16_t samples = mpeg_frame_samples[ver][lyr];
uint8_t slot_size = mpeg_slot_size[lyr];
// In-between calculations
float bps = static_cast<float>(samples) / 8.0f;
float fsize =
((bps * static_cast<float>(bitrate)) / static_cast<float>(samprate)) +
((pad) ? slot_size : 0);
// ORBIS_LOG_TODO(__FUNCTION__, (uint16_t)ver, (uint16_t)lyr,
// (uint16_t)pad, (uint16_t)brx, (uint16_t)srx, bitrate,
// samprate, samples, (uint16_t)slot_size, bps, fsize,
// static_cast<uint16_t>(fsize));
// Frame sizes are truncated integers
return static_cast<uint16_t>(fsize);
}
inline constexpr orbis::uint32_t kAACFreq[12] = {96000, 88200, 64000, 48000,
44100, 32000, 24000, 22050,
16000, 12000, 11025, 8000};
struct MP3CodecInfoSideband {
orbis::uint32_t header;
orbis::uint8_t unk0;
orbis::uint8_t unk1;
orbis::uint8_t unk2;
orbis::uint8_t unk3;
orbis::uint8_t unk4;
orbis::uint8_t unk5;
orbis::uint16_t unk6;
orbis::uint16_t unk7;
orbis::uint16_t unk8;
};
struct AACCodecInfoSideband {
orbis::uint32_t heaac;
orbis::uint32_t unk0;
};
enum AACHeaderType { AAC_ADTS = 1, AAC_RAW = 2 };
static AVSampleFormat ajmToAvFormat(ajm::Format ajmFormat) {
switch (ajmFormat) {
case ajm::Format::S16:
return AV_SAMPLE_FMT_S16;
case ajm::Format::S32:
return AV_SAMPLE_FMT_S32;
case ajm::Format::Float:
return AV_SAMPLE_FMT_FLTP;
}
return AV_SAMPLE_FMT_NONE;
}
struct FFmpegCodecInstance : ajm::CodecInstance {
std::uint64_t runJob(const ajm::Job &job) override { return 0; }
void reset() override {}
};
struct AACInstance {
AACHeaderType headerType;
orbis::uint32_t sampleRate;
};
// struct Instance {
// orbis::shared_mutex mtx;
// CodecId codecId;
// ChannelCount maxChannels;
// Format outputFormat;
// At9Instance at9;
// AACInstance aac;
// orbis::kvector<std::byte> inputBuffer;
// orbis::kvector<std::byte> outputBuffer;
// AVCodecContext *codecCtx;
// SwrContext *resampler;
// orbis::uint32_t processedSamples;
// SidebandGaplessDecode gapless;
// SidebandFormat lastDecode;
// };
struct MP3FFmpegCodecInstance : FFmpegCodecInstance {
std::uint64_t runJob(const ajm::Job &job) override { return 0; }
void reset() override {}
};
struct AACFFmpegCodecInstance : FFmpegCodecInstance {
std::uint64_t runJob(const ajm::Job &job) override { return 0; }
void reset() override {}
};
struct MP3FFmpegCodec : ajm::Codec {
orbis::ErrorCode createInstance(orbis::Ref<ajm::CodecInstance> *instance,
std::uint32_t unk0,
std::uint64_t flags) override {
auto mp3 = orbis::Ref{orbis::knew<MP3FFmpegCodecInstance>()};
*instance = mp3;
return {};
}
};
struct AACFFmpegCodec : ajm::Codec {
orbis::ErrorCode createInstance(orbis::Ref<ajm::CodecInstance> *instance,
std::uint32_t unk0,
std::uint64_t flags) override {
auto aac = orbis::Ref{orbis::knew<AACFFmpegCodecInstance>()};
*instance = aac;
return {};
}
};
void createFFmpegCodecs(AjmDevice *ajm) {
ajm->createCodec<AACFFmpegCodec>(ajm::CodecId::AAC);
ajm->createCodec<MP3FFmpegCodec>(ajm::CodecId::MP3);
}

View file

@ -0,0 +1,3 @@
#pragma once
void createFFmpegCodecs(class AjmDevice *ajm);

View file

@ -7,224 +7,251 @@
#include "orbis/utils/Logs.hpp"
#include <cstdint>
#include <cstring>
#include <print>
#include <rx/atScopeExit.hpp>
#include <rx/hexdump.hpp>
extern "C" {
#include <libatrac9/decoder.h>
#include <libatrac9/libatrac9.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/codec_internal.h>
#include <libavcodec/packet.h>
#include <libavformat/avformat.h>
#include <libavutil/mem.h>
#include <libavutil/opt.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
namespace ajm {
enum class Opcode : std::uint8_t {
RunBufferRa = 1,
ControlBufferRa = 2,
Flags = 4,
ReturnAddress = 6,
JobBufferOutputRa = 17,
JobBufferSidebandRa = 18,
};
struct InstructionHeader {
orbis::uint32_t id;
orbis::uint32_t len;
};
static_assert(sizeof(InstructionHeader) == 0x8);
struct OpcodeHeader {
orbis::uint32_t opcode;
[[nodiscard]] Opcode getOpcode() const {
// ORBIS_LOG_ERROR(__FUNCTION__, opcode);
if (auto loType = static_cast<Opcode>(opcode & 0xf);
loType == Opcode::ReturnAddress || loType == Opcode::Flags) {
return loType;
}
return static_cast<Opcode>(opcode & 0x1f);
}
};
struct ReturnAddress {
orbis::uint32_t opcode;
orbis::uint32_t unk; // 0, padding?
orbis::ptr<void> returnAddress;
};
static_assert(sizeof(ReturnAddress) == 0x10);
struct BatchJobControlBufferRa {
orbis::uint32_t opcode;
orbis::uint32_t sidebandInputSize;
orbis::ptr<std::byte> pSidebandInput;
orbis::uint32_t flagsHi;
orbis::uint32_t flagsLo;
orbis::uint32_t commandId;
orbis::uint32_t sidebandOutputSize;
orbis::ptr<std::byte> pSidebandOutput;
std::uint64_t getFlags() { return ((uint64_t)flagsHi << 0x1a) | flagsLo; }
};
static_assert(sizeof(BatchJobControlBufferRa) == 0x28);
struct BatchJobInputBufferRa {
orbis::uint32_t opcode;
orbis::uint32_t szInputSize;
orbis::ptr<std::byte> pInput;
};
static_assert(sizeof(BatchJobInputBufferRa) == 0x10);
struct BatchJobFlagsRa {
orbis::uint32_t flagsHi;
orbis::uint32_t flagsLo;
};
static_assert(sizeof(BatchJobFlagsRa) == 0x8);
struct BatchJobOutputBufferRa {
orbis::uint32_t opcode;
orbis::uint32_t outputSize;
orbis::ptr<std::byte> pOutput;
};
static_assert(sizeof(BatchJobOutputBufferRa) == 0x10);
struct BatchJobSidebandBufferRa {
orbis::uint32_t opcode;
orbis::uint32_t sidebandSize;
orbis::ptr<std::byte> pSideband;
};
static_assert(sizeof(BatchJobSidebandBufferRa) == 0x10);
static Job decodeJob(const std::byte *ptr, const std::byte *endPtr) {
Job result{};
while (ptr < endPtr) {
auto typed = (ajm::OpcodeHeader *)ptr;
switch (typed->getOpcode()) {
case ajm::Opcode::ReturnAddress: {
// ReturnAddress *ra = (ReturnAddress *)jobPtr;
// ORBIS_LOG_ERROR(__FUNCTION__, request, "return address",
// ra->opcode,
// ra->unk, ra->returnAddress);
ptr += sizeof(ajm::ReturnAddress);
break;
}
case ajm::Opcode::ControlBufferRa: {
auto *ctrl = (ajm::BatchJobControlBufferRa *)ptr;
auto *sidebandResult =
reinterpret_cast<ajm::SidebandResult *>(ctrl->pSidebandOutput);
*sidebandResult = {};
result.pSidebandResult = sidebandResult;
result.controlFlags = ctrl->getFlags();
ptr += sizeof(ajm::BatchJobControlBufferRa);
break;
}
case ajm::Opcode::RunBufferRa: {
auto *job = (ajm::BatchJobInputBufferRa *)ptr;
// ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobInputBufferRa",
// job->opcode, job->szInputSize, job->pInput);
// rx::hexdump({(std::byte*) job->pInput, job->szInputSize});
result.inputBuffers.push_back({job->pInput, job->szInputSize});
ptr += sizeof(ajm::BatchJobInputBufferRa);
break;
}
case ajm::Opcode::Flags: {
auto *job = (ajm::BatchJobFlagsRa *)ptr;
// ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobFlagsRa",
// job->flagsHi, job->flagsLo);
result.flags = ((orbis::uint64_t)job->flagsHi << 0x1a) | job->flagsLo;
ptr += sizeof(ajm::BatchJobFlagsRa);
break;
}
case ajm::Opcode::JobBufferOutputRa: {
auto *job = (ajm::BatchJobOutputBufferRa *)ptr;
// ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobOutputBufferRa",
// job->opcode, job->outputSize, job->pOutput);
result.outputBuffers.push_back({job->pOutput, job->outputSize});
result.totalOutputSize += job->outputSize;
ptr += sizeof(ajm::BatchJobOutputBufferRa);
break;
}
case ajm::Opcode::JobBufferSidebandRa: {
auto *job = (ajm::BatchJobSidebandBufferRa *)ptr;
// ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobSidebandBufferRa",
// job->opcode, job->sidebandSize, job->pSideband);
result.pSideband = job->pSideband;
result.sidebandSize = job->sidebandSize;
ptr += sizeof(ajm::BatchJobSidebandBufferRa);
break;
}
default:
ORBIS_LOG_ERROR(__FUNCTION__, "unexpected job opcode",
typed->getOpcode());
ptr = endPtr;
break;
}
}
return result;
}
} // namespace ajm
struct AjmFile : orbis::File {};
enum {
AJM_RESULT_INVALID_DATA = 0x2,
AJM_RESULT_INVALID_PARAMETER = 0x4,
AJM_RESULT_PARTIAL_INPUT = 0x8,
AJM_RESULT_NOT_ENOUGH_ROOM = 0x10,
AJM_RESULT_STREAM_CHANGE = 0x20,
AJM_RESULT_TOO_MANY_CHANNELS = 0x40,
AJM_RESULT_UNSUPPORTED_FLAG = 0x80,
AJM_RESULT_SIDEBAND_TRUNCATED = 0x100,
AJM_RESULT_PRIORITY_PASSED = 0x200,
AJM_RESULT_FATAL = 0x80000000,
AJM_RESULT_CODEC_ERROR = 0x40000000,
enum AjmIoctlRequest {
AJM_IOCTL_CONTEXT_UNREGISTER = 0xc0288900,
AJM_IOCTL_MODULE_REGISTER = 0xc0288903,
AJM_IOCTL_MODULE_UNREGISTER = 0xc0288904,
AJM_IOCTL_INSTANCE_CREATE = 0xc0288905,
AJM_IOCTL_INSTANCE_DESTROY = 0xc0288906,
AJM_IOCTL_INSTANCE_EXTEND = 0xc028890a,
AJM_IOCTL_INSTANCE_SWITCH = 0xc028890b,
AJM_IOCTL_BATCH_RUN = 0xc0288907,
AJM_IOCTL_BATCH_WAIT = 0xc0288908,
};
struct AjmDevice : IoDevice {
orbis::shared_mutex mtx;
orbis::uint32_t batchId = 1; // temp
orbis::uint32_t instanceIds[AJM_CODEC_COUNT]{};
orbis::uint32_t unimplementedInstanceId = 0;
orbis::kmap<orbis::int32_t, Instance> instanceMap;
orbis::ErrorCode open(orbis::Ref<orbis::File> *file, const char *path,
std::uint32_t flags, std::uint32_t mode,
orbis::Thread *thread) override;
};
AVSampleFormat ajmToAvFormat(AJMFormat ajmFormat) {
switch (ajmFormat) {
case AJM_FORMAT_S16:
return AV_SAMPLE_FMT_S16;
case AJM_FORMAT_S32:
return AV_SAMPLE_FMT_S32;
case AJM_FORMAT_FLOAT:
return AV_SAMPLE_FMT_FLTP;
default:
return AV_SAMPLE_FMT_NONE;
}
}
void reset(Instance *instance) {
instance->gapless.skipSamples = 0;
instance->gapless.totalSamples = 0;
instance->gapless.totalSkippedSamples = 0;
instance->processedSamples = 0;
}
void resetAt9(Instance *instance) {
if (instance->at9.configData) {
Atrac9ReleaseHandle(instance->at9.handle);
instance->at9.estimatedSizeUsed = 0;
instance->at9.superFrameSize = 0;
instance->at9.framesInSuperframe = 0;
instance->at9.frameSamples = 0;
instance->at9.sampleRate = 0;
instance->at9.superFrameDataIdx = 0;
instance->at9.inputChannels = 0;
instance->at9.superFrameDataLeft = 0;
instance->at9.handle = Atrac9GetHandle();
int err = Atrac9InitDecoder(instance->at9.handle,
(uint8_t *)&instance->at9.configData);
if (err < 0) {
ORBIS_LOG_FATAL("AT9 Init Decoder error", err);
std::abort();
}
Atrac9CodecInfo pCodecInfo;
Atrac9GetCodecInfo(instance->at9.handle, &pCodecInfo);
instance->at9.frameSamples = pCodecInfo.frameSamples;
instance->at9.inputChannels = pCodecInfo.channels;
instance->at9.framesInSuperframe = pCodecInfo.framesInSuperframe;
instance->at9.superFrameDataIdx = 0;
instance->at9.superFrameSize = pCodecInfo.superframeSize;
instance->at9.superFrameDataLeft = pCodecInfo.superframeSize;
instance->at9.sampleRate = pCodecInfo.samplingRate;
}
}
static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request,
void *argp, orbis::Thread *thread) {
auto device = static_cast<AjmDevice *>(file->device.get());
// 0xc0288900 - finalize
// 0xc0288903 - module register
// 0xc0288904 - module unregister
// 0xc0288905 - instance create
// 0xc0288906 - instance destroy
// 0xc028890a - instance extend
// 0xc028890b - intasnce switch
// 0xc0288907 - start batch buffer
// 0xc0288908 - wait batch buffer
// 0xc0288900 - unregister context
if (request == 0xc0288906) {
struct InstanceDestroyArgs {
orbis::uint32_t result;
orbis::uint32_t unk0;
orbis::uint32_t instanceId;
};
auto args = reinterpret_cast<InstanceDestroyArgs *>(argp);
if (device->instanceMap.erase(args->instanceId) == 0) {
return orbis::ErrorCode::INVAL;
}
switch (AjmIoctlRequest(request)) {
case AJM_IOCTL_CONTEXT_UNREGISTER:
case AJM_IOCTL_MODULE_REGISTER:
case AJM_IOCTL_MODULE_UNREGISTER: {
struct Args {
orbis::uint32_t result;
};
auto args = reinterpret_cast<Args *>(argp);
ORBIS_LOG_ERROR(__FUNCTION__, request, args);
args->result = 0;
return {};
}
if (request == 0xc0288903 || request == 0xc0288904 || request == 0xc0288900) {
auto arg = reinterpret_cast<std::uint32_t *>(argp)[2];
ORBIS_LOG_ERROR(__FUNCTION__, request, arg);
*reinterpret_cast<std::uint64_t *>(argp) = 0;
return {};
}
if (request == 0xc0288905) {
case AJM_IOCTL_INSTANCE_CREATE: {
struct InstanceCreateArgs {
orbis::uint32_t result;
orbis::uint32_t unk0;
orbis::uint64_t flags;
orbis::uint32_t codec;
orbis::uint32_t instanceId;
ajm::PackedInstanceId instanceId;
};
auto args = reinterpret_cast<InstanceCreateArgs *>(argp);
auto codecId = AJMCodecs(args->codec);
auto codecOffset = codecId << 0xe;
if (codecId < AJM_CODEC_COUNT) {
auto codecId = ajm::CodecId(args->codec);
orbis::Ref<ajm::CodecInstance> instance;
ORBIS_RET_ON_ERROR(
device->createInstance(&instance, codecId, args->unk0, args->flags));
auto instanceId = device->addCodecInstance(codecId, instance);
args->result = 0;
args->instanceId = codecOffset + device->instanceIds[codecId]++;
auto [it, inserted] = device->instanceMap.try_emplace(args->instanceId);
assert(inserted);
auto &instance = it->second;
instance.codec = codecId;
instance.maxChannels =
AJMChannels(((args->flags & ~7) & (0xFF & ~0b11)) >> 3);
instance.outputFormat = AJMFormat((args->flags & ~7) & 0b11);
if (codecId == AJM_CODEC_At9) {
instance.at9.handle = Atrac9GetHandle();
if (instance.outputFormat == AJM_FORMAT_S16) {
instance.at9.outputFormat = kAtrac9FormatS16;
} else if (instance.outputFormat == AJM_FORMAT_S32) {
instance.at9.outputFormat = kAtrac9FormatS32;
} else if (instance.outputFormat == AJM_FORMAT_FLOAT) {
instance.at9.outputFormat = kAtrac9FormatF32;
} else {
ORBIS_LOG_FATAL("Unexpected AT9 output format",
(std::uint32_t)instance.outputFormat);
return orbis::ErrorCode::INVAL;
}
}
if (codecId == AJM_CODEC_AAC || codecId == AJM_CODEC_MP3) {
const AVCodec *codec = avcodec_find_decoder(
codecId == AJM_CODEC_AAC ? AV_CODEC_ID_AAC : AV_CODEC_ID_MP3);
if (!codec) {
ORBIS_LOG_FATAL("Codec not found", (orbis::uint32_t)codecId);
std::abort();
}
AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
if (!codecCtx) {
ORBIS_LOG_FATAL("Failed to allocate codec context");
std::abort();
}
if (int err = avcodec_open2(codecCtx, codec, nullptr); err < 0) {
ORBIS_LOG_FATAL("Could not open codec", err);
std::abort();
}
instance.codecCtx = codecCtx;
}
ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0,
args->flags, args->codec, args->instanceId,
(std::uint32_t)instance.outputFormat);
} else {
args->instanceId = codecOffset + device->unimplementedInstanceId++;
ORBIS_LOG_ERROR(__FUNCTION__, request, "unimplemented codec",
args->result, args->unk0, args->flags, args->codec,
args->instanceId);
}
args->instanceId = ajm::PackedInstanceId::create(codecId, instanceId);
return {};
}
if (request == 0xc0288907) {
struct StartBatchBufferArgs {
case AJM_IOCTL_INSTANCE_DESTROY: {
struct InstanceDestroyArgs {
orbis::uint32_t result;
orbis::uint32_t unk0;
ajm::PackedInstanceId instanceId;
};
auto args = reinterpret_cast<InstanceDestroyArgs *>(argp);
auto packedInstanceId = args->instanceId;
ORBIS_RET_ON_ERROR(device->removeInstance(
packedInstanceId.getCodecId(), packedInstanceId.getInstanceId()));
args->result = 0;
return {};
}
case AJM_IOCTL_INSTANCE_EXTEND:
std::println(stderr, "ajm instance extend");
std::abort();
return {};
case AJM_IOCTL_INSTANCE_SWITCH:
std::println(stderr, "ajm instance switch");
std::abort();
return {};
case AJM_IOCTL_BATCH_RUN: {
struct BatchRunBufferArgs {
orbis::uint32_t result;
orbis::uint32_t unk0;
std::byte *pBatch;
orbis::uint32_t batchSize;
orbis::uint32_t priority;
orbis::uint64_t batchError;
orbis::uint32_t batchId;
ajm::BatchId batchId;
};
auto args = reinterpret_cast<StartBatchBufferArgs *>(argp);
args->result = 0;
args->batchId = device->batchId++;
auto args = reinterpret_cast<BatchRunBufferArgs *>(argp);
// ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0,
// args->pBatch, args->batchSize, args->priority,
// args->batchError, args->batchId);
@ -233,488 +260,64 @@ static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request,
auto ptr = args->pBatch;
auto endPtr = args->pBatch + args->batchSize;
std::map<orbis::Ref<ajm::CodecInstance>, std::vector<ajm::Job>> runJobMap;
while (ptr < endPtr) {
auto header = (InstructionHeader *)ptr;
auto instanceId = (header->id >> 6) & 0xfffff;
auto jobPtr = ptr + sizeof(InstructionHeader);
auto header = (ajm::InstructionHeader *)ptr;
auto instanceId = ajm::PackedInstanceId{(header->id >> 6) & 0xfffff};
auto jobPtr = ptr + sizeof(ajm::InstructionHeader);
auto endJobPtr = ptr + header->len;
// TODO: handle unimplemented codecs, so auto create instance for now
auto &instance = device->instanceMap[instanceId];
auto runJob = ajm::decodeJob(jobPtr, endJobPtr);
ptr = endJobPtr;
instance.inputBuffer.clear();
RunJob runJob{};
while (jobPtr < endJobPtr) {
auto typed = (OpcodeHeader *)jobPtr;
switch (typed->getOpcode()) {
case Opcode::ReturnAddress: {
// ReturnAddress *ra = (ReturnAddress *)jobPtr;
// ORBIS_LOG_ERROR(__FUNCTION__, request, "return address",
// ra->opcode,
// ra->unk, ra->returnAddress);
jobPtr += sizeof(ReturnAddress);
break;
}
case Opcode::ControlBufferRa: {
runJob.control = true;
auto *ctrl = (BatchJobControlBufferRa *)jobPtr;
auto *result =
reinterpret_cast<AJMSidebandResult *>(ctrl->pSidebandOutput);
*result = {};
auto instance = device->getInstance(instanceId.getCodecId(),
instanceId.getInstanceId());
ORBIS_LOG_ERROR(__FUNCTION__, request, "control buffer", ctrl->opcode,
ctrl->commandId, ctrl->flagsHi, ctrl->flagsLo,
ctrl->sidebandInputSize, ctrl->sidebandOutputSize);
if (ctrl->getFlags() & CONTROL_RESET) {
reset(&instance);
if (instance.codec == AJM_CODEC_At9) {
resetAt9(&instance);
}
if (instance == nullptr) {
return orbis::ErrorCode::BADF;
}
if (ctrl->getFlags() & CONTROL_INITIALIZE) {
if (instance.codec == AJM_CODEC_At9) {
struct InitalizeBuffer {
orbis::uint32_t configData;
orbis::int32_t unk0[2];
};
auto *initializeBuffer = (InitalizeBuffer *)ctrl->pSidebandInput;
instance.at9.configData = initializeBuffer->configData;
reset(&instance);
resetAt9(&instance);
orbis::uint32_t maxChannels =
instance.maxChannels == AJM_CHANNEL_DEFAULT
? 2
: instance.maxChannels;
orbis::uint32_t outputChannels =
instance.at9.inputChannels > maxChannels
? maxChannels
: instance.at9.inputChannels;
// TODO: check max channels
ORBIS_LOG_TODO("CONTROL_INITIALIZE", instance.at9.inputChannels,
instance.at9.sampleRate, instance.at9.frameSamples,
instance.at9.superFrameSize, maxChannels,
outputChannels, initializeBuffer->configData,
(orbis::uint32_t)instance.outputFormat);
} else if (instance.codec == AJM_CODEC_AAC) {
struct InitializeBuffer {
orbis::uint32_t headerIndex;
orbis::uint32_t sampleRateIndex;
};
auto *initializeBuffer = (InitializeBuffer *)ctrl->pSidebandInput;
instance.aac.headerType =
AACHeaderType(initializeBuffer->headerIndex);
instance.aac.sampleRate =
AACFreq[initializeBuffer->sampleRateIndex];
}
}
if (ctrl->getFlags() & SIDEBAND_GAPLESS_DECODE) {
struct InitializeBuffer {
orbis::uint32_t totalSamples;
orbis::uint16_t skipSamples;
orbis::uint16_t totalSkippedSamples;
};
auto *initializeBuffer = (InitializeBuffer *)ctrl->pSidebandInput;
if (initializeBuffer->totalSamples > 0) {
instance.gapless.totalSamples = initializeBuffer->totalSamples;
}
if (initializeBuffer->skipSamples > 0) {
instance.gapless.skipSamples = initializeBuffer->skipSamples;
}
ORBIS_LOG_TODO("SIDEBAND_GAPLESS_DECODE",
instance.gapless.skipSamples,
instance.gapless.totalSamples);
}
jobPtr += sizeof(BatchJobControlBufferRa);
break;
}
case Opcode::RunBufferRa: {
auto *job = (BatchJobInputBufferRa *)jobPtr;
// ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobInputBufferRa",
// job->opcode, job->szInputSize, job->pInput);
auto offset = instance.inputBuffer.size();
instance.inputBuffer.resize(offset + job->szInputSize);
std::memcpy(instance.inputBuffer.data() + offset, job->pInput,
job->szInputSize);
// rx::hexdump({(std::byte*) job->pInput, job->szInputSize});
jobPtr += sizeof(BatchJobInputBufferRa);
break;
}
case Opcode::Flags: {
auto *job = (BatchJobFlagsRa *)jobPtr;
// ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobFlagsRa",
// job->flagsHi, job->flagsLo);
runJob.flags = ((orbis::uint64_t)job->flagsHi << 0x1a) | job->flagsLo;
jobPtr += sizeof(BatchJobFlagsRa);
break;
}
case Opcode::JobBufferOutputRa: {
auto *job = (BatchJobOutputBufferRa *)jobPtr;
// ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobOutputBufferRa",
// job->opcode, job->outputSize, job->pOutput);
runJob.outputBuffers.push_back({job->pOutput, job->outputSize});
runJob.totalOutputSize += job->outputSize;
jobPtr += sizeof(BatchJobOutputBufferRa);
break;
}
case Opcode::JobBufferSidebandRa: {
auto *job = (BatchJobSidebandBufferRa *)jobPtr;
// ORBIS_LOG_ERROR(__FUNCTION__, request, "BatchJobSidebandBufferRa",
// job->opcode, job->sidebandSize, job->pSideband);
runJob.pSideband = job->pSideband;
runJob.sidebandSize = job->sidebandSize;
jobPtr += sizeof(BatchJobSidebandBufferRa);
break;
}
default:
jobPtr = endJobPtr;
break;
}
}
ptr = jobPtr;
if (!runJob.control && instanceId >= 0xC000) {
auto *result = reinterpret_cast<AJMSidebandResult *>(runJob.pSideband);
result->result = 0;
result->codecResult = 0;
if (runJob.flags & SIDEBAND_STREAM) {
auto *stream =
reinterpret_cast<AJMSidebandStream *>(runJob.pSideband + 8);
stream->inputSize = instance.inputBuffer.size();
stream->outputSize = runJob.totalOutputSize;
}
} else if (!runJob.control) {
// orbis::uint32_t maxChannels =
// instance.maxChannels == AJM_CHANNEL_DEFAULT ? 2
// :
// instance.maxChannels;
auto *result = reinterpret_cast<AJMSidebandResult *>(runJob.pSideband);
*result = {};
orbis::uint32_t totalDecodedBytes = 0;
orbis::uint32_t outputWritten = 0;
orbis::uint32_t framesProcessed = 0;
orbis::uint32_t samplesCount = 0;
if (!instance.inputBuffer.empty() && runJob.totalOutputSize != 0) {
instance.inputBuffer.reserve(instance.inputBuffer.size() +
AV_INPUT_BUFFER_PADDING_SIZE);
AVPacket *pkt = av_packet_alloc();
rx::atScopeExit _free_pkt([&] { av_packet_free(&pkt); });
AVFrame *frame = av_frame_alloc();
rx::atScopeExit _free_frame([&] { av_frame_free(&frame); });
do {
if (instance.codec == AJM_CODEC_At9 &&
instance.at9.frameSamples == 0) {
break;
}
if (totalDecodedBytes >= instance.inputBuffer.size()) {
break;
auto [it, inserted] = runJobMap.try_emplace(std::move(instance));
it->second.push_back(std::move(runJob));
}
framesProcessed++;
auto batch = orbis::knew<ajm::Batch>();
std::uint32_t inputFrameSize = 0;
std::uint32_t outputBufferSize = 0;
if (instance.codec == AJM_CODEC_At9) {
inputFrameSize = 4;
outputBufferSize = av_samples_get_buffer_size(
nullptr, instance.at9.inputChannels,
instance.at9.frameSamples,
ajmToAvFormat(instance.outputFormat), 0);
} else if (instance.codec == AJM_CODEC_MP3) {
if (instance.inputBuffer.size() - totalDecodedBytes < 4) {
result->result = AJM_RESULT_INVALID_DATA;
break;
}
inputFrameSize = get_mp3_data_size(
(orbis::uint8_t *)(instance.inputBuffer.data() +
totalDecodedBytes));
if (inputFrameSize == 0) {
result->result = AJM_RESULT_INVALID_DATA;
break;
}
} else if (instance.codec == AJM_CODEC_AAC) {
inputFrameSize = instance.inputBuffer.size() - totalDecodedBytes;
}
if (inputFrameSize >
instance.inputBuffer.size() - totalDecodedBytes) {
result->result |= AJM_RESULT_PARTIAL_INPUT;
break;
}
if (outputBufferSize > runJob.totalOutputSize - outputWritten) {
result->result |= AJM_RESULT_NOT_ENOUGH_ROOM;
break;
}
pkt->data =
(std::uint8_t *)instance.inputBuffer.data() + totalDecodedBytes;
pkt->size = inputFrameSize;
if (instance.codec == AJM_CODEC_At9) {
orbis::int32_t bytesUsed = 0;
instance.outputBuffer.resize(outputBufferSize);
int err =
Atrac9Decode(instance.at9.handle,
instance.inputBuffer.data() + totalDecodedBytes,
instance.outputBuffer.data(),
instance.at9.outputFormat, &bytesUsed);
if (err != ERR_SUCCESS) {
rx::hexdump(
std::span(instance.inputBuffer).subspan(totalDecodedBytes));
ORBIS_LOG_FATAL("Could not decode frame", err,
instance.at9.estimatedSizeUsed,
instance.at9.superFrameSize,
instance.at9.frameSamples, instance.at9.handle,
totalDecodedBytes, outputWritten);
std::abort();
}
instance.at9.estimatedSizeUsed =
static_cast<orbis::uint32_t>(bytesUsed);
instance.at9.superFrameDataLeft -= bytesUsed;
instance.at9.superFrameDataIdx++;
if (instance.at9.superFrameDataIdx ==
instance.at9.framesInSuperframe) {
instance.at9.estimatedSizeUsed +=
instance.at9.superFrameDataLeft;
instance.at9.superFrameDataIdx = 0;
instance.at9.superFrameDataLeft = instance.at9.superFrameSize;
}
samplesCount = instance.at9.frameSamples;
inputFrameSize = instance.at9.estimatedSizeUsed;
instance.lastDecode.channels =
AJMChannels(instance.at9.inputChannels);
instance.lastDecode.sampleRate = instance.at9.sampleRate;
// ORBIS_LOG_TODO("at9 decode", instance.at9.estimatedSizeUsed,
// instance.at9.superFrameDataLeft,
// instance.at9.superFrameDataIdx,
// instance.at9.framesInSuperframe);
} else if (instance.codec == AJM_CODEC_MP3) {
int ret = avcodec_send_packet(instance.codecCtx, pkt);
if (ret < 0) {
ORBIS_LOG_FATAL("Error sending packet for decoding", ret);
std::abort();
}
ret = avcodec_receive_frame(instance.codecCtx, frame);
if (ret < 0) {
ORBIS_LOG_FATAL("Error during decoding");
std::abort();
}
outputBufferSize = av_samples_get_buffer_size(
nullptr, frame->ch_layout.nb_channels, frame->nb_samples,
ajmToAvFormat(instance.outputFormat), 0);
samplesCount = frame->nb_samples;
instance.lastDecode.channels =
AJMChannels(frame->ch_layout.nb_channels);
instance.lastDecode.sampleRate = frame->sample_rate;
} else if (instance.codec == AJM_CODEC_AAC) {
// HACK: to avoid writing a bunch of useless calls
// we simply call this method directly (but it can be very
// unstable)
int gotFrame;
int len =
ffcodec(instance.codecCtx->codec)
->cb.decode(instance.codecCtx, frame, &gotFrame, pkt);
if (len < 0) {
ORBIS_LOG_FATAL("Error during decoding");
std::abort();
}
outputBufferSize = av_samples_get_buffer_size(
nullptr, frame->ch_layout.nb_channels, frame->nb_samples,
ajmToAvFormat(instance.outputFormat), 0);
samplesCount = frame->nb_samples;
inputFrameSize = len;
instance.lastDecode.channels =
AJMChannels(frame->ch_layout.nb_channels);
instance.lastDecode.sampleRate = frame->sample_rate;
}
if (inputFrameSize >
instance.inputBuffer.size() - totalDecodedBytes) {
result->result |= AJM_RESULT_PARTIAL_INPUT;
break;
}
if (outputBufferSize > runJob.totalOutputSize - outputWritten) {
result->result |= AJM_RESULT_NOT_ENOUGH_ROOM;
break;
}
totalDecodedBytes += inputFrameSize;
if (instance.gapless.skipSamples > 0 ||
instance.gapless.totalSamples > 0) {
if (instance.gapless.totalSkippedSamples <
instance.gapless.skipSamples ||
instance.processedSamples > instance.gapless.totalSamples) {
instance.gapless.totalSkippedSamples += samplesCount;
continue;
}
}
// at least three codecs outputs in float
// and mp3 support sample rate resample (TODO), so made resampling
// with swr
if (instance.codec != AJM_CODEC_At9) {
instance.outputBuffer.resize(outputBufferSize);
if (instance.resampler == nullptr) {
instance.resampler = swr_alloc();
auto resampler = instance.resampler;
AVChannelLayout chLayout;
av_channel_layout_default(&chLayout,
frame->ch_layout.nb_channels);
av_opt_set_chlayout(resampler, "in_chlayout", &chLayout, 0);
av_opt_set_chlayout(resampler, "out_chlayout", &chLayout, 0);
av_opt_set_int(resampler, "in_sample_rate", frame->sample_rate,
0);
av_opt_set_int(resampler, "out_sample_rate", frame->sample_rate,
0);
av_opt_set_sample_fmt(resampler, "in_sample_fmt",
ajmToAvFormat(AJM_FORMAT_FLOAT), 0);
av_opt_set_sample_fmt(resampler, "out_sample_fmt",
ajmToAvFormat(instance.outputFormat), 0);
if (swr_init(resampler) < 0) {
ORBIS_LOG_FATAL(
"Failed to initialize the resampling context");
std::abort();
}
}
auto *outputBuffer = reinterpret_cast<orbis::uint8_t *>(
instance.outputBuffer.data());
int nb_samples = swr_convert(
instance.resampler, &outputBuffer, frame->nb_samples,
frame->extended_data, frame->nb_samples);
if (nb_samples != frame->nb_samples) {
ORBIS_LOG_FATAL("Error while converting");
std::abort();
}
}
std::uint32_t bufferOutputWritten = 0;
for (std::size_t bufferOffset = 0;
auto buffer : runJob.outputBuffers) {
if (bufferOffset <= outputWritten &&
bufferOffset + buffer.size > outputWritten) {
auto byteOffset = outputWritten - bufferOffset;
auto size = std::min(buffer.size - byteOffset,
instance.outputBuffer.size() -
bufferOutputWritten);
ORBIS_RET_ON_ERROR(orbis::uwrite(
buffer.pOutput + byteOffset,
instance.outputBuffer.data() + bufferOutputWritten, size));
bufferOutputWritten += size;
outputWritten += size;
if (bufferOutputWritten >= instance.outputBuffer.size()) {
break;
}
}
bufferOffset += buffer.size;
}
instance.processedSamples += samplesCount;
} while ((runJob.flags & RUN_MULTIPLE_FRAMES) != 0);
}
orbis::int64_t currentSize = sizeof(AJMSidebandResult);
if (runJob.flags & SIDEBAND_STREAM) {
// ORBIS_LOG_TODO("SIDEBAND_STREAM", currentSize, inputReaded,
// outputWritten, instance.processedSamples);
auto *stream = reinterpret_cast<AJMSidebandStream *>(
runJob.pSideband + currentSize);
stream->inputSize = totalDecodedBytes;
stream->outputSize = outputWritten;
stream->decodedSamples = instance.processedSamples;
currentSize += sizeof(AJMSidebandStream);
}
if (runJob.flags & SIDEBAND_FORMAT) {
// ORBIS_LOG_TODO("SIDEBAND_FORMAT", currentSize);
auto *format = reinterpret_cast<AJMSidebandFormat *>(
runJob.pSideband + currentSize);
format->channels = AJMChannels(instance.lastDecode.channels);
format->sampleRate = instance.lastDecode.sampleRate;
format->sampleFormat = instance.outputFormat;
// TODO: channel mask and bitrate
currentSize += sizeof(AJMSidebandFormat);
}
if (runJob.flags & SIDEBAND_GAPLESS_DECODE) {
// ORBIS_LOG_TODO("SIDEBAND_GAPLESS_DECODE", currentSize);
auto *gapless = reinterpret_cast<AJMSidebandGaplessDecode *>(
runJob.pSideband + currentSize);
gapless->skipSamples = instance.gapless.skipSamples;
gapless->totalSamples = instance.gapless.totalSamples;
gapless->totalSkippedSamples = instance.gapless.totalSkippedSamples;
currentSize += sizeof(AJMSidebandGaplessDecode);
}
if (runJob.flags & RUN_GET_CODEC_INFO) {
// ORBIS_LOG_TODO("RUN_GET_CODEC_INFO");
if (instance.codec == AJM_CODEC_At9) {
auto *info = reinterpret_cast<AJMAt9CodecInfoSideband *>(
runJob.pSideband + currentSize);
info->superFrameSize = instance.at9.superFrameSize;
info->framesInSuperFrame = instance.at9.framesInSuperframe;
info->frameSamples = instance.at9.frameSamples;
currentSize += sizeof(AJMAt9CodecInfoSideband);
} else if (instance.codec == AJM_CODEC_MP3) {
// TODO
auto *info = reinterpret_cast<AJMMP3CodecInfoSideband *>(
runJob.pSideband + currentSize);
currentSize += sizeof(AJMMP3CodecInfoSideband);
} else if (instance.codec == AJM_CODEC_AAC) {
// TODO
auto *info = reinterpret_cast<AJMAACCodecInfoSideband *>(
runJob.pSideband + currentSize);
currentSize += sizeof(AJMAACCodecInfoSideband);
}
}
if (runJob.flags & RUN_MULTIPLE_FRAMES) {
// ORBIS_LOG_TODO("RUN_MULTIPLE_FRAMES", framesProcessed);
auto *multipleFrames = reinterpret_cast<AJMSidebandMultipleFrames *>(
runJob.pSideband + currentSize);
multipleFrames->framesProcessed = framesProcessed;
currentSize += sizeof(AJMSidebandMultipleFrames);
}
}
for (auto &[instance, runJobs] : runJobMap) {
instance->runBatch(batch, std::move(runJobs));
}
args->result = 0;
args->batchId = device->addBatch(batch);
return {};
}
if (request == 0xc0288908) {
case AJM_IOCTL_BATCH_WAIT: {
struct Args {
orbis::uint32_t unk0;
orbis::uint32_t result;
orbis::uint32_t unk1;
orbis::uint32_t batchId;
ajm::BatchId batchId;
orbis::uint32_t timeout;
orbis::uint64_t batchError;
};
auto args = reinterpret_cast<Args *>(argp);
args->unk0 = 0;
auto batch = device->getBatch(args->batchId);
if (batch == nullptr) {
return orbis::ErrorCode::BADF;
}
// ORBIS_LOG_ERROR(__FUNCTION__, request, args->unk0, args->unk1,
// args->batchId, args->timeout, args->batchError);
// thread->where();
std::uint64_t batchError = 0;
ORBIS_RET_ON_ERROR(batch->wait(args->timeout, &batchError));
args->result = 0;
args->batchError = batchError;
return {};
}
}
ORBIS_LOG_FATAL("Unhandled AJM ioctl", request);
thread->where();

View file

@ -1,368 +1,401 @@
#pragma once
#include "libatrac9/libatrac9.h"
#include "io-device.hpp"
#include "orbis-config.hpp"
#include "orbis/KernelAllocator.hpp"
#include "orbis/utils/SharedMutex.hpp"
// #include "orbis/utils/Logs.hpp"
#include "orbis/error.hpp"
#include "orbis/utils/IdMap.hpp"
#include "orbis/utils/Rc.hpp"
#include "orbis/utils/SharedAtomic.hpp"
#include "rx/refl.hpp"
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
}
#include <deque>
#include <limits>
#include <mutex>
#include <print>
#include <stop_token>
#include <thread>
#include <utility>
enum class Opcode : std::uint8_t {
RunBufferRa = 1,
ControlBufferRa = 2,
Flags = 4,
ReturnAddress = 6,
JobBufferOutputRa = 17,
JobBufferSidebandRa = 18,
namespace ajm {
enum ControlFlags {
kControlInitialize = 0x4000,
kControlReset = 0x2000,
};
struct InstructionHeader {
orbis::uint32_t id;
orbis::uint32_t len;
enum RunFlags {
kRunMultipleFrames = 0x1000,
kRunGetCodecInfo = 0x800,
};
static_assert(sizeof(InstructionHeader) == 0x8);
enum SidebandFlags {
kSidebandStream = 0x800000000000,
kSidebandFormat = 0x400000000000,
kSidebandGaplessDecode = 0x200000000000,
};
struct OpcodeHeader {
orbis::uint32_t opcode;
enum {
kResultInvalidData = 0x2,
kResultInvalidParameter = 0x4,
kResultPartialInput = 0x8,
kResultNotEnoughRoom = 0x10,
kResultStreamChange = 0x20,
kResultTooManyChannels = 0x40,
kResultUnsupportedFlag = 0x80,
kResultSidebandTruncated = 0x100,
kResultPriorityPassed = 0x200,
kResultCodecError = 0x40000000,
kResultFatal = 0x80000000,
};
Opcode getOpcode() const {
// ORBIS_LOG_ERROR(__FUNCTION__, opcode);
if (auto loType = static_cast<Opcode>(opcode & 0xf);
loType == Opcode::ReturnAddress || loType == Opcode::Flags) {
return loType;
enum class CodecId : std::uint32_t {
MP3 = 0,
At9 = 1,
AAC = 2,
};
enum class ChannelCount : orbis::uint32_t {
Default,
_1,
_2,
_3,
_4,
_5,
_6,
_8,
};
enum class Format : orbis::uint32_t {
S16 = 0, // default
S32 = 1,
Float = 2
};
enum class BatchId : std::uint32_t {};
enum class InstanceId : std::uint32_t {};
struct PackedInstanceId {
std::uint32_t raw;
static PackedInstanceId create(CodecId codecId, InstanceId instanceId) {
return {.raw = static_cast<std::uint32_t>(codecId) << 15 |
static_cast<std::uint32_t>(instanceId)};
}
return static_cast<Opcode>(opcode & 0x1f);
[[nodiscard]] InstanceId getInstanceId() const {
return static_cast<InstanceId>(raw & ((1 << 15) - 1));
}
};
struct ReturnAddress {
orbis::uint32_t opcode;
orbis::uint32_t unk; // 0, padding?
orbis::ptr<void> returnAddress;
};
static_assert(sizeof(ReturnAddress) == 0x10);
struct BatchJobControlBufferRa {
orbis::uint32_t opcode;
orbis::uint32_t sidebandInputSize;
orbis::ptr<std::byte> pSidebandInput;
orbis::uint32_t flagsHi;
orbis::uint32_t flagsLo;
orbis::uint32_t commandId;
orbis::uint32_t sidebandOutputSize;
orbis::ptr<std::byte> pSidebandOutput;
std::uint64_t getFlags() { return ((uint64_t)flagsHi << 0x1a) | flagsLo; }
};
static_assert(sizeof(BatchJobControlBufferRa) == 0x28);
struct BatchJobInputBufferRa {
orbis::uint32_t opcode;
orbis::uint32_t szInputSize;
orbis::ptr<std::byte> pInput;
};
static_assert(sizeof(BatchJobInputBufferRa) == 0x10);
struct BatchJobFlagsRa {
orbis::uint32_t flagsHi;
orbis::uint32_t flagsLo;
};
static_assert(sizeof(BatchJobFlagsRa) == 0x8);
struct BatchJobOutputBufferRa {
orbis::uint32_t opcode;
orbis::uint32_t outputSize;
orbis::ptr<std::byte> pOutput;
};
static_assert(sizeof(BatchJobOutputBufferRa) == 0x10);
struct BatchJobSidebandBufferRa {
orbis::uint32_t opcode;
orbis::uint32_t sidebandSize;
orbis::ptr<std::byte> pSideband;
};
static_assert(sizeof(BatchJobSidebandBufferRa) == 0x10);
struct AjmOutputBuffer {
orbis::ptr<std::byte> pOutput;
orbis::size_t size;
};
struct RunJob {
std::uint64_t flags;
std::uint32_t sidebandSize;
std::uint32_t totalOutputSize;
std::vector<AjmOutputBuffer> outputBuffers;
orbis::ptr<std::byte> pSideband;
bool control;
};
// Thanks to mystical SirNickity with 1 post
// https://hydrogenaud.io/index.php?topic=85125.msg747716#msg747716
inline constexpr uint8_t mpeg_versions[4] = {25, 0, 2, 1};
// Layers - use [layer]
inline constexpr uint8_t mpeg_layers[4] = {0, 3, 2, 1};
// Bitrates - use [version][layer][bitrate]
inline constexpr uint16_t mpeg_bitrates[4][4][16] = {
{
// Version 2.5
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,
0}, // Layer 3
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,
0}, // Layer 2
{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256,
0} // Layer 1
},
{
// Reserved
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Invalid
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Invalid
},
{
// Version 2
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,
0}, // Layer 3
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,
0}, // Layer 2
{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256,
0} // Layer 1
},
{
// Version 1
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Reserved
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
0}, // Layer 3
{0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384,
0}, // Layer 2
{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448,
0}, // Layer 1
}};
// Sample rates - use [version][srate]
inline constexpr uint16_t mpeg_srates[4][4] = {
{11025, 12000, 8000, 0}, // MPEG 2.5
{0, 0, 0, 0}, // Reserved
{22050, 24000, 16000, 0}, // MPEG 2
{44100, 48000, 32000, 0} // MPEG 1
};
// Samples per frame - use [version][layer]
inline constexpr uint16_t mpeg_frame_samples[4][4] = {
// Rsvd 3 2 1 < Layer v Version
{0, 576, 1152, 384}, // 2.5
{0, 0, 0, 0}, // Reserved
{0, 576, 1152, 384}, // 2
{0, 1152, 1152, 384} // 1
};
// Slot size (MPEG unit of measurement) - use [layer]
inline constexpr uint8_t mpeg_slot_size[4] = {0, 1, 1, 4}; // Rsvd, 3, 2, 1
constexpr uint32_t get_mp3_data_size(const uint8_t *data) {
// Quick validity check
if (((data[0] & 0xFF) != 0xFF) || ((data[1] & 0xE0) != 0xE0) // 3 sync bits
|| ((data[1] & 0x18) == 0x08) // Version rsvd
|| ((data[1] & 0x06) == 0x00) // Layer rsvd
|| ((data[2] & 0xF0) == 0xF0) // Bitrate rsvd
) {
return 0;
[[nodiscard]] CodecId getCodecId() const {
return static_cast<CodecId>(raw >> 15);
}
// 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<float>(samples) / 8.0f;
float fsize =
((bps * static_cast<float>(bitrate)) / static_cast<float>(samprate)) +
((pad) ? slot_size : 0);
// ORBIS_LOG_TODO(__FUNCTION__, (uint16_t)ver, (uint16_t)lyr,
// (uint16_t)pad, (uint16_t)brx, (uint16_t)srx, bitrate,
// samprate, samples, (uint16_t)slot_size, bps, fsize,
// static_cast<uint16_t>(fsize));
// Frame sizes are truncated integers
return static_cast<uint16_t>(fsize);
}
enum AACHeaderType { AAC_ADTS = 1, AAC_RAW = 2 };
inline constexpr orbis::uint32_t AACFreq[12] = {96000, 88200, 64000, 48000,
44100, 32000, 24000, 22050,
16000, 12000, 11025, 8000};
enum AJMCodecs : orbis::uint32_t {
AJM_CODEC_MP3 = 0,
AJM_CODEC_At9 = 1,
AJM_CODEC_AAC = 2,
AJM_CODEC_COUNT
auto operator<=>(const PackedInstanceId &) const = default;
};
enum AJMChannels : orbis::uint32_t {
AJM_CHANNEL_DEFAULT = 0,
AJM_CHANNEL_1 = 1,
AJM_CHANNEL_2 = 2,
AJM_CHANNEL_3 = 3,
AJM_CHANNEL_4 = 4,
AJM_CHANNEL_5 = 5,
AJM_CHANNEL_6 = 6,
AJM_CHANNEL_8 = 8,
};
enum AJMFormat : orbis::uint32_t {
AJM_FORMAT_S16 = 0, // default
AJM_FORMAT_S32 = 1,
AJM_FORMAT_FLOAT = 2
};
struct At9Instance {
orbis::ptr<void> handle{};
orbis::uint32_t inputChannels{};
orbis::uint32_t framesInSuperframe{};
orbis::uint32_t frameSamples{};
orbis::uint32_t superFrameDataLeft{};
orbis::uint32_t superFrameDataIdx{};
orbis::uint32_t superFrameSize{};
orbis::uint32_t estimatedSizeUsed{};
orbis::uint32_t sampleRate{};
Atrac9Format outputFormat{};
orbis::uint32_t configData;
~At9Instance() {
if (handle) {
Atrac9ReleaseHandle(handle);
}
}
};
struct AACInstance {
AACHeaderType headerType;
orbis::uint32_t sampleRate;
};
struct AJMSidebandGaplessDecode {
struct SidebandGaplessDecode {
orbis::uint32_t totalSamples;
orbis::uint16_t skipSamples;
orbis::uint16_t totalSkippedSamples;
};
struct AJMSidebandResult {
struct SidebandResult {
orbis::int32_t result;
orbis::int32_t codecResult;
};
struct AJMSidebandStream {
struct SidebandStream {
orbis::int32_t inputSize;
orbis::int32_t outputSize;
orbis::uint64_t decodedSamples;
};
struct AJMSidebandMultipleFrames {
struct SidebandMultipleFrames {
orbis::uint32_t framesProcessed;
orbis::uint32_t unk0;
};
struct AJMSidebandFormat {
AJMChannels channels;
struct SidebandFormat {
ChannelCount channels;
orbis::uint32_t unk0; // maybe channel mask?
orbis::uint32_t sampleRate;
AJMFormat sampleFormat;
Format sampleFormat;
uint32_t bitrate;
uint32_t unk1;
};
struct AJMAt9CodecInfoSideband {
orbis::uint32_t superFrameSize;
orbis::uint32_t framesInSuperFrame;
orbis::uint32_t unk0;
orbis::uint32_t frameSamples;
struct AjmBuffer {
orbis::ptr<std::byte> pData;
orbis::size_t size;
};
struct AJMMP3CodecInfoSideband {
orbis::uint32_t header;
orbis::uint8_t unk0;
orbis::uint8_t unk1;
orbis::uint8_t unk2;
orbis::uint8_t unk3;
orbis::uint8_t unk4;
orbis::uint8_t unk5;
orbis::uint16_t unk6;
orbis::uint16_t unk7;
orbis::uint16_t unk8;
struct Job {
std::uint64_t flags;
std::uint32_t sidebandSize;
std::uint32_t totalOutputSize;
std::vector<AjmBuffer> inputBuffers;
std::vector<AjmBuffer> outputBuffers;
orbis::ptr<std::byte> pSideband;
orbis::ptr<SidebandResult> pSidebandResult;
std::uint64_t controlFlags;
};
struct AJMAACCodecInfoSideband {
orbis::uint32_t heaac;
orbis::uint32_t unk0;
class InstanceBatch;
struct Batch : orbis::RcBase {
enum Status {
Queued,
Cancelled,
Complete,
};
private:
std::atomic<std::uint32_t> mCompleteJobs{0};
orbis::shared_atomic32 mStatus{Status::Queued};
std::atomic<std::uint64_t> mBatchError{0};
std::vector<orbis::Ref<InstanceBatch>> mInstanceBatches;
public:
[[nodiscard]] orbis::ErrorCode wait(std::uint32_t timeout,
std::uint64_t *batchError) {
std::chrono::microseconds usecTimeout;
if (timeout == std::numeric_limits<std::uint32_t>::max()) {
usecTimeout = std::chrono::microseconds::max();
} else {
usecTimeout = std::chrono::microseconds(timeout);
}
while (true) {
auto status = mStatus.load(std::memory_order::relaxed);
if (status != Status::Queued) {
*batchError = mBatchError.load(std::memory_order::relaxed);
break;
}
auto errc = mStatus.wait(status, usecTimeout);
if (errc != std::errc{}) {
return orbis::toErrorCode(errc);
}
}
return {};
}
[[nodiscard]] Status getStatus() const {
return Status(mStatus.load(std::memory_order::acquire));
}
void setStatus(Status status) {
if (status == Status::Queued) {
return;
}
std::uint32_t prevStatus = Status::Queued;
if (mStatus.compare_exchange_strong(prevStatus, status,
std::memory_order::relaxed,
std::memory_order::release)) {
mStatus.notify_all();
}
}
void handleCompletion(std::uint64_t batchError) {
if (mBatchError != 0) {
std::uint64_t prevError = 0;
mBatchError.compare_exchange_strong(prevError, batchError);
}
if (mCompleteJobs.fetch_add(1) == mInstanceBatches.size() - 1) {
setStatus(Status::Complete);
}
}
};
struct Instance {
class InstanceBatch : public orbis::RcBase {
orbis::Ref<Batch> mBatch;
std::vector<Job> mJobs;
public:
InstanceBatch() = default;
InstanceBatch(orbis::Ref<Batch> batch, std::vector<Job> jobs)
: mBatch(std::move(batch)), mJobs(std::move(jobs)) {}
std::span<const Job> getJobs() { return mJobs; }
void complete(std::uint64_t batchError) {
mBatch->handleCompletion(batchError);
}
[[nodiscard]] bool inProgress() const {
return mBatch->getStatus() == Batch::Status::Queued;
}
};
class CodecInstance : public orbis::RcBase {
std::mutex mWorkerMutex;
std::condition_variable mWorkerCv;
std::deque<orbis::Ref<InstanceBatch>> mJobQueue;
std::jthread mWorkerThread{
[this](const std::stop_token &stopToken) { workerEntry(stopToken); }};
public:
virtual ~CodecInstance() {
std::lock_guard lock(mWorkerMutex);
mWorkerThread.request_stop();
mWorkerCv.notify_all();
}
virtual std::uint64_t runJob(const Job &job) = 0;
virtual void reset() = 0;
void runBatch(orbis::Ref<Batch> batch, std::vector<Job> jobs) {
auto instanceBatch = orbis::Ref<InstanceBatch>(
orbis::knew<InstanceBatch>(std::move(batch), std::move(jobs)));
std::lock_guard lock(mWorkerMutex);
mJobQueue.push_back(instanceBatch);
mWorkerCv.notify_one();
}
private:
void workerEntry(const std::stop_token &stopToken) {
while (!stopToken.stop_requested()) {
orbis::Ref<InstanceBatch> batch;
{
std::unique_lock lock(mWorkerMutex);
while (mJobQueue.empty()) {
mWorkerCv.wait(lock);
if (stopToken.stop_requested()) {
return;
}
}
}
if (batch == nullptr) {
continue;
}
if (batch->inProgress()) {
std::uint64_t error = 0;
for (auto &job : batch->getJobs()) {
auto result = runJob(job);
if (result != 0) {
error = result;
}
}
batch->complete(error);
}
}
}
};
struct Codec : orbis::RcBase {
virtual ~Codec() = default;
[[nodiscard]] virtual orbis::ErrorCode
createInstance(orbis::Ref<ajm::CodecInstance> *instance, std::uint32_t unk0,
std::uint64_t flags) = 0;
};
inline constexpr auto kCodecCount = rx::fieldCount<ajm::CodecId>;
} // namespace ajm
struct AjmDevice : IoDevice {
orbis::shared_mutex mtx;
AJMCodecs codec;
AJMChannels maxChannels;
AJMFormat outputFormat;
At9Instance at9;
AACInstance aac;
orbis::kvector<std::byte> inputBuffer;
orbis::kvector<std::byte> outputBuffer;
AVCodecContext *codecCtx;
SwrContext *resampler;
orbis::uint32_t lastBatchId;
// TODO: use AJMSidebandGaplessDecode for these variables
AJMSidebandGaplessDecode gapless;
orbis::uint32_t processedSamples;
AJMSidebandFormat lastDecode;
orbis::Ref<ajm::Codec> codecs[ajm::kCodecCount];
orbis::RcIdMap<ajm::CodecInstance, ajm::InstanceId>
mCodecInstances[ajm::kCodecCount];
~Instance() {
if (resampler) {
swr_free(&resampler);
orbis::RcIdMap<ajm::Batch, ajm::BatchId, 4096, 1> batchMap;
orbis::ErrorCode open(orbis::Ref<orbis::File> *file, const char *path,
std::uint32_t flags, std::uint32_t mode,
orbis::Thread *thread) override;
template <typename InstanceT, typename... ArgsT>
void createCodec(ajm::CodecId id, ArgsT &&...args) {
auto instance = orbis::knew<InstanceT>(std::forward<ArgsT>(args)...);
codecs[std::to_underlying(id)] = instance;
}
if (codecCtx) {
avcodec_free_context(&codecCtx);
[[nodiscard]] orbis::ErrorCode
createInstance(orbis::Ref<ajm::CodecInstance> *instance, ajm::CodecId codecId,
std::uint32_t unk0, std::uint64_t flags) {
auto rawCodecId = std::to_underlying(codecId);
if (rawCodecId >= ajm::kCodecCount) {
return orbis::ErrorCode::INVAL;
}
auto codec = codecs[rawCodecId];
if (codec == nullptr) {
return orbis::ErrorCode::SRCH;
}
return codec->createInstance(instance, unk0, flags);
}
[[nodiscard]] orbis::ErrorCode removeInstance(ajm::CodecId codecId,
ajm::InstanceId instanceId) {
auto rawCodecId = std::to_underlying(codecId);
if (rawCodecId >= ajm::kCodecCount) {
return orbis::ErrorCode::INVAL;
}
if (!mCodecInstances[rawCodecId].close(instanceId)) {
return orbis::ErrorCode::BADF;
}
return {};
}
[[nodiscard]] orbis::Ref<ajm::CodecInstance>
getInstance(ajm::CodecId codecId, ajm::InstanceId instanceId) {
auto rawCodecId = std::to_underlying(codecId);
if (rawCodecId >= ajm::kCodecCount) {
return {};
}
return mCodecInstances[rawCodecId].get(instanceId);
}
[[nodiscard]] ajm::InstanceId
addCodecInstance(ajm::CodecId codecId,
orbis::Ref<ajm::CodecInstance> instance) {
auto &instances = mCodecInstances[std::to_underlying(codecId)];
auto id = instances.insert(std::move(instance));
if (id == std::remove_cvref_t<decltype(instances)>::npos) {
std::println(stderr, "out of codec instances");
std::abort();
}
return id;
}
[[nodiscard]] ajm::BatchId addBatch(ajm::Batch *batch) {
auto id = batchMap.insert(batch);
if (id == decltype(batchMap)::npos) {
std::println(stderr, "out of batches");
std::abort();
}
return id;
}
[[nodiscard]] orbis::Ref<ajm::Batch> getBatch(ajm::BatchId id) const {
return batchMap.get(id);
}
[[nodiscard]] orbis::ErrorCode removeBatch(ajm::BatchId id) {
if (batchMap.close(id)) {
return orbis::ErrorCode::BADF;
}
return {};
}
};
enum ControlFlags {
CONTROL_INITIALIZE = 0x4000,
CONTROL_RESET = 0x2000,
};
enum RunFlags {
RUN_MULTIPLE_FRAMES = 0x1000,
RUN_GET_CODEC_INFO = 0x800,
};
enum SidebandFlags {
SIDEBAND_STREAM = 0x800000000000,
SIDEBAND_FORMAT = 0x400000000000,
SIDEBAND_GAPLESS_DECODE = 0x200000000000,
};

View file

@ -1,8 +1,11 @@
#include "audio/AlsaDevice.hpp"
#include "audio/At9Codec.hpp"
#include "audio/FFmpegCodecs.hpp"
#include "backtrace.hpp"
#include "gpu/DeviceCtl.hpp"
#include "io-device.hpp"
#include "io-devices.hpp"
#include "iodev/ajm.hpp"
#include "iodev/mbus.hpp"
#include "iodev/mbus_av.hpp"
#include "ipmi.hpp"
@ -337,6 +340,10 @@ static void ps4InitDev() {
auto analogAudioDevice = nullAudioDevice;
auto spdifAudioDevice = nullAudioDevice;
auto ajm = static_cast<AjmDevice *>(createAjmCharacterDevice());
createAt9Codec(ajm);
createFFmpegCodecs(ajm);
vfs::addDevice("dmem0", createDmemCharacterDevice(0));
vfs::addDevice("npdrm", createNpdrmCharacterDevice());
vfs::addDevice("icc_configuration", createIccConfigurationCharacterDevice());
@ -369,7 +376,7 @@ static void ps4InitDev() {
vfs::addDevice("gc", createGcCharacterDevice());
vfs::addDevice("rng", createRngCharacterDevice());
vfs::addDevice("sbl_srv", createSblSrvCharacterDevice());
vfs::addDevice("ajm", createAjmCharacterDevice());
vfs::addDevice("ajm", ajm);
vfs::addDevice("urandom", createUrandomCharacterDevice());
vfs::addDevice("mbus", mbus);
vfs::addDevice("metadbg", createMetaDbgCharacterDevice());