mirror of
https://github.com/RPCSX/rpcsx.git
synced 2025-12-06 07:12:14 +01:00
[WIP] ajm: async instances
This commit is contained in:
parent
8440940945
commit
ce8d4dcf16
|
|
@ -9,6 +9,8 @@ add_library(orbis::kernel::config ALIAS standalone-config)
|
||||||
add_executable(rpcsx
|
add_executable(rpcsx
|
||||||
audio/AudioDevice.cpp
|
audio/AudioDevice.cpp
|
||||||
audio/AlsaDevice.cpp
|
audio/AlsaDevice.cpp
|
||||||
|
audio/At9Codec.cpp
|
||||||
|
audio/FFmpegCodecs.cpp
|
||||||
|
|
||||||
iodev/ajm.cpp
|
iodev/ajm.cpp
|
||||||
iodev/blockpool.cpp
|
iodev/blockpool.cpp
|
||||||
|
|
|
||||||
54
rpcsx/audio/At9Codec.cpp
Normal file
54
rpcsx/audio/At9Codec.cpp
Normal 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
3
rpcsx/audio/At9Codec.hpp
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void createAt9Codec(class AjmDevice *ajm);
|
||||||
216
rpcsx/audio/FFmpegCodecs.cpp
Normal file
216
rpcsx/audio/FFmpegCodecs.cpp
Normal 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);
|
||||||
|
}
|
||||||
3
rpcsx/audio/FFmpegCodecs.hpp
Normal file
3
rpcsx/audio/FFmpegCodecs.hpp
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void createFFmpegCodecs(class AjmDevice *ajm);
|
||||||
|
|
@ -7,224 +7,251 @@
|
||||||
#include "orbis/utils/Logs.hpp"
|
#include "orbis/utils/Logs.hpp"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <print>
|
||||||
#include <rx/atScopeExit.hpp>
|
#include <rx/atScopeExit.hpp>
|
||||||
#include <rx/hexdump.hpp>
|
#include <rx/hexdump.hpp>
|
||||||
|
|
||||||
extern "C" {
|
namespace ajm {
|
||||||
#include <libatrac9/decoder.h>
|
enum class Opcode : std::uint8_t {
|
||||||
#include <libatrac9/libatrac9.h>
|
RunBufferRa = 1,
|
||||||
#include <libavcodec/avcodec.h>
|
ControlBufferRa = 2,
|
||||||
#include <libavcodec/codec_internal.h>
|
Flags = 4,
|
||||||
#include <libavcodec/packet.h>
|
ReturnAddress = 6,
|
||||||
#include <libavformat/avformat.h>
|
JobBufferOutputRa = 17,
|
||||||
#include <libavutil/mem.h>
|
JobBufferSidebandRa = 18,
|
||||||
#include <libavutil/opt.h>
|
};
|
||||||
#include <libavutil/samplefmt.h>
|
|
||||||
#include <libswresample/swresample.h>
|
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 {};
|
struct AjmFile : orbis::File {};
|
||||||
|
|
||||||
enum {
|
enum AjmIoctlRequest {
|
||||||
AJM_RESULT_INVALID_DATA = 0x2,
|
AJM_IOCTL_CONTEXT_UNREGISTER = 0xc0288900,
|
||||||
AJM_RESULT_INVALID_PARAMETER = 0x4,
|
AJM_IOCTL_MODULE_REGISTER = 0xc0288903,
|
||||||
AJM_RESULT_PARTIAL_INPUT = 0x8,
|
AJM_IOCTL_MODULE_UNREGISTER = 0xc0288904,
|
||||||
AJM_RESULT_NOT_ENOUGH_ROOM = 0x10,
|
AJM_IOCTL_INSTANCE_CREATE = 0xc0288905,
|
||||||
AJM_RESULT_STREAM_CHANGE = 0x20,
|
AJM_IOCTL_INSTANCE_DESTROY = 0xc0288906,
|
||||||
AJM_RESULT_TOO_MANY_CHANNELS = 0x40,
|
AJM_IOCTL_INSTANCE_EXTEND = 0xc028890a,
|
||||||
AJM_RESULT_UNSUPPORTED_FLAG = 0x80,
|
AJM_IOCTL_INSTANCE_SWITCH = 0xc028890b,
|
||||||
AJM_RESULT_SIDEBAND_TRUNCATED = 0x100,
|
AJM_IOCTL_BATCH_RUN = 0xc0288907,
|
||||||
AJM_RESULT_PRIORITY_PASSED = 0x200,
|
AJM_IOCTL_BATCH_WAIT = 0xc0288908,
|
||||||
AJM_RESULT_FATAL = 0x80000000,
|
|
||||||
AJM_RESULT_CODEC_ERROR = 0x40000000,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
static orbis::ErrorCode ajm_ioctl(orbis::File *file, std::uint64_t request,
|
||||||
void *argp, orbis::Thread *thread) {
|
void *argp, orbis::Thread *thread) {
|
||||||
|
|
||||||
auto device = static_cast<AjmDevice *>(file->device.get());
|
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;
|
args->result = 0;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request == 0xc0288903 || request == 0xc0288904 || request == 0xc0288900) {
|
case AJM_IOCTL_INSTANCE_CREATE: {
|
||||||
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) {
|
|
||||||
struct InstanceCreateArgs {
|
struct InstanceCreateArgs {
|
||||||
orbis::uint32_t result;
|
orbis::uint32_t result;
|
||||||
orbis::uint32_t unk0;
|
orbis::uint32_t unk0;
|
||||||
orbis::uint64_t flags;
|
orbis::uint64_t flags;
|
||||||
orbis::uint32_t codec;
|
orbis::uint32_t codec;
|
||||||
orbis::uint32_t instanceId;
|
ajm::PackedInstanceId instanceId;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto args = reinterpret_cast<InstanceCreateArgs *>(argp);
|
auto args = reinterpret_cast<InstanceCreateArgs *>(argp);
|
||||||
auto codecId = AJMCodecs(args->codec);
|
auto codecId = ajm::CodecId(args->codec);
|
||||||
auto codecOffset = codecId << 0xe;
|
orbis::Ref<ajm::CodecInstance> instance;
|
||||||
if (codecId < AJM_CODEC_COUNT) {
|
ORBIS_RET_ON_ERROR(
|
||||||
|
device->createInstance(&instance, codecId, args->unk0, args->flags));
|
||||||
|
|
||||||
|
auto instanceId = device->addCodecInstance(codecId, instance);
|
||||||
args->result = 0;
|
args->result = 0;
|
||||||
args->instanceId = codecOffset + device->instanceIds[codecId]++;
|
args->instanceId = ajm::PackedInstanceId::create(codecId, instanceId);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request == 0xc0288907) {
|
case AJM_IOCTL_INSTANCE_DESTROY: {
|
||||||
struct StartBatchBufferArgs {
|
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 result;
|
||||||
orbis::uint32_t unk0;
|
orbis::uint32_t unk0;
|
||||||
std::byte *pBatch;
|
std::byte *pBatch;
|
||||||
orbis::uint32_t batchSize;
|
orbis::uint32_t batchSize;
|
||||||
orbis::uint32_t priority;
|
orbis::uint32_t priority;
|
||||||
orbis::uint64_t batchError;
|
orbis::uint64_t batchError;
|
||||||
orbis::uint32_t batchId;
|
ajm::BatchId batchId;
|
||||||
};
|
};
|
||||||
auto args = reinterpret_cast<StartBatchBufferArgs *>(argp);
|
|
||||||
args->result = 0;
|
auto args = reinterpret_cast<BatchRunBufferArgs *>(argp);
|
||||||
args->batchId = device->batchId++;
|
|
||||||
// ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0,
|
// ORBIS_LOG_ERROR(__FUNCTION__, request, args->result, args->unk0,
|
||||||
// args->pBatch, args->batchSize, args->priority,
|
// args->pBatch, args->batchSize, args->priority,
|
||||||
// args->batchError, args->batchId);
|
// 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 ptr = args->pBatch;
|
||||||
auto endPtr = args->pBatch + args->batchSize;
|
auto endPtr = args->pBatch + args->batchSize;
|
||||||
|
|
||||||
|
std::map<orbis::Ref<ajm::CodecInstance>, std::vector<ajm::Job>> runJobMap;
|
||||||
|
|
||||||
while (ptr < endPtr) {
|
while (ptr < endPtr) {
|
||||||
auto header = (InstructionHeader *)ptr;
|
auto header = (ajm::InstructionHeader *)ptr;
|
||||||
auto instanceId = (header->id >> 6) & 0xfffff;
|
auto instanceId = ajm::PackedInstanceId{(header->id >> 6) & 0xfffff};
|
||||||
auto jobPtr = ptr + sizeof(InstructionHeader);
|
auto jobPtr = ptr + sizeof(ajm::InstructionHeader);
|
||||||
auto endJobPtr = ptr + header->len;
|
auto endJobPtr = ptr + header->len;
|
||||||
// TODO: handle unimplemented codecs, so auto create instance for now
|
// 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();
|
auto instance = device->getInstance(instanceId.getCodecId(),
|
||||||
RunJob runJob{};
|
instanceId.getInstanceId());
|
||||||
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 = {};
|
|
||||||
|
|
||||||
ORBIS_LOG_ERROR(__FUNCTION__, request, "control buffer", ctrl->opcode,
|
if (instance == nullptr) {
|
||||||
ctrl->commandId, ctrl->flagsHi, ctrl->flagsLo,
|
return orbis::ErrorCode::BADF;
|
||||||
ctrl->sidebandInputSize, ctrl->sidebandOutputSize);
|
|
||||||
if (ctrl->getFlags() & CONTROL_RESET) {
|
|
||||||
reset(&instance);
|
|
||||||
if (instance.codec == AJM_CODEC_At9) {
|
|
||||||
resetAt9(&instance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctrl->getFlags() & CONTROL_INITIALIZE) {
|
auto [it, inserted] = runJobMap.try_emplace(std::move(instance));
|
||||||
if (instance.codec == AJM_CODEC_At9) {
|
it->second.push_back(std::move(runJob));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
framesProcessed++;
|
auto batch = orbis::knew<ajm::Batch>();
|
||||||
|
|
||||||
std::uint32_t inputFrameSize = 0;
|
for (auto &[instance, runJobs] : runJobMap) {
|
||||||
std::uint32_t outputBufferSize = 0;
|
instance->runBatch(batch, std::move(runJobs));
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args->result = 0;
|
||||||
|
args->batchId = device->addBatch(batch);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
case AJM_IOCTL_BATCH_WAIT: {
|
||||||
if (request == 0xc0288908) {
|
|
||||||
struct Args {
|
struct Args {
|
||||||
orbis::uint32_t unk0;
|
orbis::uint32_t result;
|
||||||
orbis::uint32_t unk1;
|
orbis::uint32_t unk1;
|
||||||
orbis::uint32_t batchId;
|
ajm::BatchId batchId;
|
||||||
orbis::uint32_t timeout;
|
orbis::uint32_t timeout;
|
||||||
orbis::uint64_t batchError;
|
orbis::uint64_t batchError;
|
||||||
};
|
};
|
||||||
auto args = reinterpret_cast<Args *>(argp);
|
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,
|
// ORBIS_LOG_ERROR(__FUNCTION__, request, args->unk0, args->unk1,
|
||||||
// args->batchId, args->timeout, args->batchError);
|
// args->batchId, args->timeout, args->batchError);
|
||||||
// thread->where();
|
// thread->where();
|
||||||
|
|
||||||
|
std::uint64_t batchError = 0;
|
||||||
|
ORBIS_RET_ON_ERROR(batch->wait(args->timeout, &batchError));
|
||||||
|
args->result = 0;
|
||||||
|
args->batchError = batchError;
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ORBIS_LOG_FATAL("Unhandled AJM ioctl", request);
|
ORBIS_LOG_FATAL("Unhandled AJM ioctl", request);
|
||||||
thread->where();
|
thread->where();
|
||||||
|
|
|
||||||
|
|
@ -1,368 +1,401 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "libatrac9/libatrac9.h"
|
#include "io-device.hpp"
|
||||||
#include "orbis-config.hpp"
|
#include "orbis-config.hpp"
|
||||||
#include "orbis/KernelAllocator.hpp"
|
#include "orbis/KernelAllocator.hpp"
|
||||||
#include "orbis/utils/SharedMutex.hpp"
|
#include "orbis/error.hpp"
|
||||||
// #include "orbis/utils/Logs.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>
|
#include <cstdint>
|
||||||
extern "C" {
|
#include <deque>
|
||||||
#include <libavcodec/avcodec.h>
|
#include <limits>
|
||||||
#include <libavutil/samplefmt.h>
|
#include <mutex>
|
||||||
#include <libswresample/swresample.h>
|
#include <print>
|
||||||
|
#include <stop_token>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace ajm {
|
||||||
|
enum ControlFlags {
|
||||||
|
kControlInitialize = 0x4000,
|
||||||
|
kControlReset = 0x2000,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum RunFlags {
|
||||||
|
kRunMultipleFrames = 0x1000,
|
||||||
|
kRunGetCodecInfo = 0x800,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SidebandFlags {
|
||||||
|
kSidebandStream = 0x800000000000,
|
||||||
|
kSidebandFormat = 0x400000000000,
|
||||||
|
kSidebandGaplessDecode = 0x200000000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
kResultInvalidData = 0x2,
|
||||||
|
kResultInvalidParameter = 0x4,
|
||||||
|
kResultPartialInput = 0x8,
|
||||||
|
kResultNotEnoughRoom = 0x10,
|
||||||
|
kResultStreamChange = 0x20,
|
||||||
|
kResultTooManyChannels = 0x40,
|
||||||
|
kResultUnsupportedFlag = 0x80,
|
||||||
|
kResultSidebandTruncated = 0x100,
|
||||||
|
kResultPriorityPassed = 0x200,
|
||||||
|
kResultCodecError = 0x40000000,
|
||||||
|
kResultFatal = 0x80000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
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)};
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Opcode : std::uint8_t {
|
[[nodiscard]] InstanceId getInstanceId() const {
|
||||||
RunBufferRa = 1,
|
return static_cast<InstanceId>(raw & ((1 << 15) - 1));
|
||||||
ControlBufferRa = 2,
|
}
|
||||||
Flags = 4,
|
[[nodiscard]] CodecId getCodecId() const {
|
||||||
ReturnAddress = 6,
|
return static_cast<CodecId>(raw >> 15);
|
||||||
JobBufferOutputRa = 17,
|
|
||||||
JobBufferSidebandRa = 18,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InstructionHeader {
|
|
||||||
orbis::uint32_t id;
|
|
||||||
orbis::uint32_t len;
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(InstructionHeader) == 0x8);
|
|
||||||
|
|
||||||
struct OpcodeHeader {
|
|
||||||
orbis::uint32_t opcode;
|
|
||||||
|
|
||||||
Opcode getOpcode() const {
|
|
||||||
// ORBIS_LOG_ERROR(__FUNCTION__, opcode);
|
|
||||||
if (auto loType = static_cast<Opcode>(opcode & 0xf);
|
|
||||||
loType == Opcode::ReturnAddress || loType == Opcode::Flags) {
|
|
||||||
return loType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return static_cast<Opcode>(opcode & 0x1f);
|
auto operator<=>(const PackedInstanceId &) const = default;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ReturnAddress {
|
struct SidebandGaplessDecode {
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
|
||||||
orbis::uint32_t totalSamples;
|
orbis::uint32_t totalSamples;
|
||||||
orbis::uint16_t skipSamples;
|
orbis::uint16_t skipSamples;
|
||||||
orbis::uint16_t totalSkippedSamples;
|
orbis::uint16_t totalSkippedSamples;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AJMSidebandResult {
|
struct SidebandResult {
|
||||||
orbis::int32_t result;
|
orbis::int32_t result;
|
||||||
orbis::int32_t codecResult;
|
orbis::int32_t codecResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AJMSidebandStream {
|
struct SidebandStream {
|
||||||
orbis::int32_t inputSize;
|
orbis::int32_t inputSize;
|
||||||
orbis::int32_t outputSize;
|
orbis::int32_t outputSize;
|
||||||
orbis::uint64_t decodedSamples;
|
orbis::uint64_t decodedSamples;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AJMSidebandMultipleFrames {
|
struct SidebandMultipleFrames {
|
||||||
orbis::uint32_t framesProcessed;
|
orbis::uint32_t framesProcessed;
|
||||||
orbis::uint32_t unk0;
|
orbis::uint32_t unk0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AJMSidebandFormat {
|
struct SidebandFormat {
|
||||||
AJMChannels channels;
|
ChannelCount channels;
|
||||||
orbis::uint32_t unk0; // maybe channel mask?
|
orbis::uint32_t unk0; // maybe channel mask?
|
||||||
orbis::uint32_t sampleRate;
|
orbis::uint32_t sampleRate;
|
||||||
AJMFormat sampleFormat;
|
Format sampleFormat;
|
||||||
uint32_t bitrate;
|
uint32_t bitrate;
|
||||||
uint32_t unk1;
|
uint32_t unk1;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AJMAt9CodecInfoSideband {
|
struct AjmBuffer {
|
||||||
orbis::uint32_t superFrameSize;
|
orbis::ptr<std::byte> pData;
|
||||||
orbis::uint32_t framesInSuperFrame;
|
orbis::size_t size;
|
||||||
orbis::uint32_t unk0;
|
|
||||||
orbis::uint32_t frameSamples;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AJMMP3CodecInfoSideband {
|
struct Job {
|
||||||
orbis::uint32_t header;
|
std::uint64_t flags;
|
||||||
orbis::uint8_t unk0;
|
std::uint32_t sidebandSize;
|
||||||
orbis::uint8_t unk1;
|
std::uint32_t totalOutputSize;
|
||||||
orbis::uint8_t unk2;
|
std::vector<AjmBuffer> inputBuffers;
|
||||||
orbis::uint8_t unk3;
|
std::vector<AjmBuffer> outputBuffers;
|
||||||
orbis::uint8_t unk4;
|
orbis::ptr<std::byte> pSideband;
|
||||||
orbis::uint8_t unk5;
|
orbis::ptr<SidebandResult> pSidebandResult;
|
||||||
orbis::uint16_t unk6;
|
std::uint64_t controlFlags;
|
||||||
orbis::uint16_t unk7;
|
|
||||||
orbis::uint16_t unk8;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AJMAACCodecInfoSideband {
|
class InstanceBatch;
|
||||||
orbis::uint32_t heaac;
|
|
||||||
orbis::uint32_t unk0;
|
struct Batch : orbis::RcBase {
|
||||||
|
enum Status {
|
||||||
|
Queued,
|
||||||
|
Cancelled,
|
||||||
|
Complete,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Instance {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
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;
|
orbis::Ref<ajm::Codec> codecs[ajm::kCodecCount];
|
||||||
SwrContext *resampler;
|
orbis::RcIdMap<ajm::CodecInstance, ajm::InstanceId>
|
||||||
orbis::uint32_t lastBatchId;
|
mCodecInstances[ajm::kCodecCount];
|
||||||
// TODO: use AJMSidebandGaplessDecode for these variables
|
|
||||||
AJMSidebandGaplessDecode gapless;
|
|
||||||
orbis::uint32_t processedSamples;
|
|
||||||
AJMSidebandFormat lastDecode;
|
|
||||||
|
|
||||||
~Instance() {
|
orbis::RcIdMap<ajm::Batch, ajm::BatchId, 4096, 1> batchMap;
|
||||||
if (resampler) {
|
|
||||||
swr_free(&resampler);
|
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,
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
#include "audio/AlsaDevice.hpp"
|
#include "audio/AlsaDevice.hpp"
|
||||||
|
#include "audio/At9Codec.hpp"
|
||||||
|
#include "audio/FFmpegCodecs.hpp"
|
||||||
#include "backtrace.hpp"
|
#include "backtrace.hpp"
|
||||||
#include "gpu/DeviceCtl.hpp"
|
#include "gpu/DeviceCtl.hpp"
|
||||||
#include "io-device.hpp"
|
#include "io-device.hpp"
|
||||||
#include "io-devices.hpp"
|
#include "io-devices.hpp"
|
||||||
|
#include "iodev/ajm.hpp"
|
||||||
#include "iodev/mbus.hpp"
|
#include "iodev/mbus.hpp"
|
||||||
#include "iodev/mbus_av.hpp"
|
#include "iodev/mbus_av.hpp"
|
||||||
#include "ipmi.hpp"
|
#include "ipmi.hpp"
|
||||||
|
|
@ -337,6 +340,10 @@ static void ps4InitDev() {
|
||||||
auto analogAudioDevice = nullAudioDevice;
|
auto analogAudioDevice = nullAudioDevice;
|
||||||
auto spdifAudioDevice = nullAudioDevice;
|
auto spdifAudioDevice = nullAudioDevice;
|
||||||
|
|
||||||
|
auto ajm = static_cast<AjmDevice *>(createAjmCharacterDevice());
|
||||||
|
createAt9Codec(ajm);
|
||||||
|
createFFmpegCodecs(ajm);
|
||||||
|
|
||||||
vfs::addDevice("dmem0", createDmemCharacterDevice(0));
|
vfs::addDevice("dmem0", createDmemCharacterDevice(0));
|
||||||
vfs::addDevice("npdrm", createNpdrmCharacterDevice());
|
vfs::addDevice("npdrm", createNpdrmCharacterDevice());
|
||||||
vfs::addDevice("icc_configuration", createIccConfigurationCharacterDevice());
|
vfs::addDevice("icc_configuration", createIccConfigurationCharacterDevice());
|
||||||
|
|
@ -369,7 +376,7 @@ static void ps4InitDev() {
|
||||||
vfs::addDevice("gc", createGcCharacterDevice());
|
vfs::addDevice("gc", createGcCharacterDevice());
|
||||||
vfs::addDevice("rng", createRngCharacterDevice());
|
vfs::addDevice("rng", createRngCharacterDevice());
|
||||||
vfs::addDevice("sbl_srv", createSblSrvCharacterDevice());
|
vfs::addDevice("sbl_srv", createSblSrvCharacterDevice());
|
||||||
vfs::addDevice("ajm", createAjmCharacterDevice());
|
vfs::addDevice("ajm", ajm);
|
||||||
vfs::addDevice("urandom", createUrandomCharacterDevice());
|
vfs::addDevice("urandom", createUrandomCharacterDevice());
|
||||||
vfs::addDevice("mbus", mbus);
|
vfs::addDevice("mbus", mbus);
|
||||||
vfs::addDevice("metadbg", createMetaDbgCharacterDevice());
|
vfs::addDevice("metadbg", createMetaDbgCharacterDevice());
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue