mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-08-04 16:32:39 +00:00
refactor half ajm
This commit is contained in:
parent
f818f3e69f
commit
b52632c924
@ -179,18 +179,24 @@ set(AUDIO_CORE src/audio_core/sdl_audio.cpp
|
|||||||
src/audio_core/sdl_audio.h
|
src/audio_core/sdl_audio.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(AJM_LIB src/core/libraries/ajm/ajm.cpp
|
||||||
|
src/core/libraries/ajm/ajm.h
|
||||||
|
src/core/libraries/ajm/ajm_at9.cpp
|
||||||
|
src/core/libraries/ajm/ajm_at9.h
|
||||||
|
src/core/libraries/ajm/ajm_batch.cpp
|
||||||
|
src/core/libraries/ajm/ajm_batch.h
|
||||||
|
src/core/libraries/ajm/ajm_context.cpp
|
||||||
|
src/core/libraries/ajm/ajm_context.h
|
||||||
|
src/core/libraries/ajm/ajm_error.h
|
||||||
|
src/core/libraries/ajm/ajm_instance.h
|
||||||
|
src/core/libraries/ajm/ajm_mp3.cpp
|
||||||
|
src/core/libraries/ajm/ajm_mp3.h
|
||||||
|
)
|
||||||
|
|
||||||
set(AUDIO_LIB src/core/libraries/audio/audioin.cpp
|
set(AUDIO_LIB src/core/libraries/audio/audioin.cpp
|
||||||
src/core/libraries/audio/audioin.h
|
src/core/libraries/audio/audioin.h
|
||||||
src/core/libraries/audio/audioout.cpp
|
src/core/libraries/audio/audioout.cpp
|
||||||
src/core/libraries/audio/audioout.h
|
src/core/libraries/audio/audioout.h
|
||||||
src/core/libraries/ajm/ajm.cpp
|
|
||||||
src/core/libraries/ajm/ajm.h
|
|
||||||
src/core/libraries/ajm/ajm_instance.h
|
|
||||||
src/core/libraries/ajm/ajm_error.h
|
|
||||||
src/core/libraries/ajm/ajm_mp3.cpp
|
|
||||||
src/core/libraries/ajm/ajm_mp3.h
|
|
||||||
src/core/libraries/ajm/ajm_at9.cpp
|
|
||||||
src/core/libraries/ajm/ajm_at9.h
|
|
||||||
src/core/libraries/ngs2/ngs2.cpp
|
src/core/libraries/ngs2/ngs2.cpp
|
||||||
src/core/libraries/ngs2/ngs2.h
|
src/core/libraries/ngs2/ngs2.h
|
||||||
)
|
)
|
||||||
@ -499,6 +505,7 @@ set(CORE src/core/aerolib/stubs.cpp
|
|||||||
src/core/libraries/error_codes.h
|
src/core/libraries/error_codes.h
|
||||||
src/core/libraries/libs.h
|
src/core/libraries/libs.h
|
||||||
src/core/libraries/libs.cpp
|
src/core/libraries/libs.cpp
|
||||||
|
${AJM_LIB}
|
||||||
${AUDIO_LIB}
|
${AUDIO_LIB}
|
||||||
${GNM_LIB}
|
${GNM_LIB}
|
||||||
${KERNEL_LIB}
|
${KERNEL_LIB}
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <boost/icl/interval_set.hpp>
|
|
||||||
|
|
||||||
#include <limits>
|
|
||||||
#include <optional>
|
|
||||||
#include <shared_mutex>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
template <class Index, class T, Index MaxIndex = std::numeric_limits<Index>::max()>
|
|
||||||
class IndexedResources {
|
|
||||||
public:
|
|
||||||
IndexedResources() {
|
|
||||||
m_free_indices += boost::icl::interval<Index>::closed(0, MaxIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class... Types>
|
|
||||||
std::optional<Index> Create(Types&&... args) {
|
|
||||||
std::unique_lock lock{m_mutex};
|
|
||||||
if (m_free_indices.empty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
auto index = first(*m_free_indices.begin());
|
|
||||||
m_free_indices -= index;
|
|
||||||
m_container.emplace(index, T(std::forward<Types>(args)...));
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Destroy(Index index) {
|
|
||||||
std::unique_lock lock{m_mutex};
|
|
||||||
if (m_container.erase(index) > 0) {
|
|
||||||
m_free_indices += index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::reference_wrapper<T>> Get(Index index) {
|
|
||||||
std::shared_lock lock{m_mutex};
|
|
||||||
auto it = m_container.find(index);
|
|
||||||
if (it == m_container.end()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_mutex m_mutex;
|
|
||||||
std::unordered_map<Index, T> m_container;
|
|
||||||
boost::icl::interval_set<Index> m_free_indices;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Common
|
|
63
src/common/slot_array.h
Normal file
63
src/common/slot_array.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// #include <boost/icl/interval_set.hpp>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <class IndexType, class ResourceType,
|
||||||
|
IndexType MaxIndex = std::numeric_limits<IndexType>::max(), IndexType MinIndex = 0>
|
||||||
|
class SlotArray {
|
||||||
|
public:
|
||||||
|
SlotArray() {
|
||||||
|
std::iota(m_free_indices.begin(), m_free_indices.end(), MinIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class... Types>
|
||||||
|
std::optional<IndexType> Create(Types&&... args) {
|
||||||
|
if (!HasFreeSlots()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto index = m_free_indices[m_curr_cursor];
|
||||||
|
m_resources[index - MinIndex] = ResourceType(std::forward<Types>(args)...);
|
||||||
|
m_curr_cursor += 1;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Destroy(IndexType index) {
|
||||||
|
if (!m_resources[index - MinIndex].has_value()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_curr_cursor -= 1;
|
||||||
|
m_free_indices[m_curr_cursor] = index;
|
||||||
|
m_resources[index - MinIndex] = std::nullopt;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceType* Get(IndexType index) {
|
||||||
|
auto& resource = m_resources[index - MinIndex];
|
||||||
|
if (!resource.has_value()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &resource.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasFreeSlots() {
|
||||||
|
return m_curr_cursor < m_free_indices.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t m_curr_cursor = 0;
|
||||||
|
std::array<IndexType, MaxIndex - MinIndex> m_free_indices;
|
||||||
|
std::array<std::optional<ResourceType>, MaxIndex - MinIndex> m_resources;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
@ -1,202 +1,23 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <mutex>
|
|
||||||
#include <numeric>
|
|
||||||
#include <semaphore>
|
|
||||||
#include <span>
|
|
||||||
#include <thread>
|
|
||||||
#include <boost/container/small_vector.hpp>
|
|
||||||
#include <magic_enum.hpp>
|
|
||||||
#include <queue>
|
|
||||||
|
|
||||||
#include "common/alignment.h"
|
|
||||||
#include "common/assert.h"
|
|
||||||
#include "common/indexed_resources.h"
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/scope_exit.h"
|
|
||||||
#include "core/libraries/ajm/ajm.h"
|
#include "core/libraries/ajm/ajm.h"
|
||||||
#include "core/libraries/ajm/ajm_at9.h"
|
#include "core/libraries/ajm/ajm_context.h"
|
||||||
#include "core/libraries/ajm/ajm_error.h"
|
#include "core/libraries/ajm/ajm_error.h"
|
||||||
#include "core/libraries/ajm/ajm_instance.h"
|
|
||||||
#include "core/libraries/ajm/ajm_mp3.h"
|
#include "core/libraries/ajm/ajm_mp3.h"
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
|
|
||||||
extern "C" {
|
#include <magic_enum.hpp>
|
||||||
#include <libatrac9.h>
|
|
||||||
#include <libavcodec/avcodec.h>
|
|
||||||
#include <libavutil/opt.h>
|
|
||||||
#include <libswresample/swresample.h>
|
|
||||||
#include <structures.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Libraries::Ajm {
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
static constexpr u32 AJM_INSTANCE_STATISTICS = 0x80000;
|
static std::unique_ptr<AjmContext> context{};
|
||||||
|
|
||||||
static constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1;
|
|
||||||
|
|
||||||
static constexpr u32 MaxInstances = 0x2fff;
|
|
||||||
|
|
||||||
static constexpr u32 MaxBatches = 1000;
|
|
||||||
|
|
||||||
struct AjmJob {
|
|
||||||
u32 instance_id = 0;
|
|
||||||
AjmJobFlags flags = {.raw = 0};
|
|
||||||
AjmJobInput input;
|
|
||||||
AjmJobOutput output;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BatchInfo {
|
|
||||||
u32 id;
|
|
||||||
std::atomic_bool waiting{};
|
|
||||||
std::atomic_bool canceled{};
|
|
||||||
std::binary_semaphore finished{0};
|
|
||||||
boost::container::small_vector<AjmJob, 8> jobs;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class ChunkType>
|
|
||||||
ChunkType& AjmBufferExtract(u8*& p_cursor) {
|
|
||||||
auto* const result = reinterpret_cast<ChunkType*>(p_cursor);
|
|
||||||
p_cursor += sizeof(ChunkType);
|
|
||||||
return *result;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ChunkType>
|
|
||||||
void AjmBufferSkip(u8*& p_cursor) {
|
|
||||||
p_cursor += sizeof(ChunkType);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ChunkType>
|
|
||||||
ChunkType& AjmBufferPeek(u8* p_cursor) {
|
|
||||||
return *reinterpret_cast<ChunkType*>(p_cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AjmDevice {
|
|
||||||
u32 max_prio{};
|
|
||||||
u32 min_prio{};
|
|
||||||
u32 curr_cursor{};
|
|
||||||
u32 release_cursor{MaxInstances - 1};
|
|
||||||
std::array<bool, NumAjmCodecs> is_registered{};
|
|
||||||
std::array<u32, MaxInstances> free_instances{};
|
|
||||||
std::array<std::unique_ptr<AjmInstance>, MaxInstances> instances;
|
|
||||||
Common::IndexedResources<u32, std::shared_ptr<BatchInfo>, MaxBatches> batches;
|
|
||||||
std::mutex batches_mutex;
|
|
||||||
|
|
||||||
std::jthread worker_thread{};
|
|
||||||
std::queue<std::shared_ptr<BatchInfo>> batch_queue{};
|
|
||||||
std::mutex batch_queue_mutex{};
|
|
||||||
std::mutex batch_queue_cv_mutex{};
|
|
||||||
std::condition_variable_any batch_queue_cv{};
|
|
||||||
|
|
||||||
[[nodiscard]] bool IsRegistered(AjmCodecType type) const {
|
|
||||||
return is_registered[static_cast<u32>(type)];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Register(AjmCodecType type) {
|
|
||||||
is_registered[static_cast<u32>(type)] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
AjmDevice() {
|
|
||||||
std::iota(free_instances.begin(), free_instances.end(), 1);
|
|
||||||
worker_thread = std::jthread([this](std::stop_token stop) { this->WorkerThread(stop); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkerThread(std::stop_token stop) {
|
|
||||||
while (!stop.stop_requested()) {
|
|
||||||
{
|
|
||||||
std::unique_lock lock(batch_queue_cv_mutex);
|
|
||||||
if (!batch_queue_cv.wait(lock, stop, [this] { return !batch_queue.empty(); })) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<BatchInfo> batch;
|
|
||||||
{
|
|
||||||
std::lock_guard lock(batch_queue_mutex);
|
|
||||||
batch = batch_queue.front();
|
|
||||||
batch_queue.pop();
|
|
||||||
}
|
|
||||||
ProcessBatch(batch->id, batch->jobs);
|
|
||||||
batch->finished.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProcessBatch(u32 id, std::span<AjmJob> jobs) {
|
|
||||||
// Perform operation requested by control flags.
|
|
||||||
for (auto& job : jobs) {
|
|
||||||
LOG_DEBUG(Lib_Ajm, "Processing job {} for instance {}. flags = {:#x}", id,
|
|
||||||
job.instance_id, job.flags.raw);
|
|
||||||
|
|
||||||
AjmInstance* p_instance = instances[job.instance_id].get();
|
|
||||||
|
|
||||||
const auto control_flags = job.flags.control_flags;
|
|
||||||
if (True(control_flags & AjmJobControlFlags::Reset)) {
|
|
||||||
LOG_INFO(Lib_Ajm, "Resetting instance {}", job.instance_id);
|
|
||||||
p_instance->Reset();
|
|
||||||
}
|
|
||||||
if (True(control_flags & AjmJobControlFlags::Initialize)) {
|
|
||||||
LOG_INFO(Lib_Ajm, "Initializing instance {}", job.instance_id);
|
|
||||||
ASSERT_MSG(job.input.init_params.has_value(),
|
|
||||||
"Initialize called without control buffer");
|
|
||||||
auto& params = job.input.init_params.value();
|
|
||||||
p_instance->Initialize(¶ms, sizeof(params));
|
|
||||||
}
|
|
||||||
if (True(control_flags & AjmJobControlFlags::Resample)) {
|
|
||||||
LOG_ERROR(Lib_Ajm, "Unimplemented: resample params");
|
|
||||||
ASSERT_MSG(job.input.resample_parameters.has_value(),
|
|
||||||
"Resample paramters are absent");
|
|
||||||
p_instance->resample_parameters = job.input.resample_parameters.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto sideband_flags = job.flags.sideband_flags;
|
|
||||||
if (True(sideband_flags & AjmJobSidebandFlags::Format)) {
|
|
||||||
ASSERT_MSG(job.input.format.has_value(), "Format parameters are absent");
|
|
||||||
p_instance->format = job.input.format.value();
|
|
||||||
}
|
|
||||||
if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode)) {
|
|
||||||
ASSERT_MSG(job.input.gapless_decode.has_value(),
|
|
||||||
"Gapless decode parameters are absent");
|
|
||||||
auto& params = job.input.gapless_decode.value();
|
|
||||||
p_instance->gapless.total_samples = params.total_samples;
|
|
||||||
p_instance->gapless.skip_samples = params.skip_samples;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!job.input.buffer.empty()) {
|
|
||||||
p_instance->Decode(&job.input, &job.output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (job.output.p_gapless_decode != nullptr) {
|
|
||||||
*job.output.p_gapless_decode = p_instance->gapless;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (job.output.p_codec_info != nullptr) {
|
|
||||||
p_instance->GetCodecInfo(job.output.p_codec_info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static std::unique_ptr<AjmDevice> dev{};
|
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id) {
|
int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id) {
|
||||||
std::shared_ptr<BatchInfo> batch{};
|
LOG_INFO(Lib_Ajm, "called context_id = {} batch_id = {}", context_id, batch_id);
|
||||||
{
|
return context->BatchCancel(batch_id);
|
||||||
std::lock_guard guard(dev->batches_mutex);
|
|
||||||
const auto opt_batch = dev->batches.Get(batch_id);
|
|
||||||
if (!opt_batch.has_value()) {
|
|
||||||
return ORBIS_AJM_ERROR_INVALID_BATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
batch = opt_batch.value().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
batch->canceled = true;
|
|
||||||
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchErrorDump() {
|
int PS4_SYSV_ABI sceAjmBatchErrorDump() {
|
||||||
@ -209,78 +30,15 @@ void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(void* p_buffer, u32 instance_id
|
|||||||
void* p_sideband_output,
|
void* p_sideband_output,
|
||||||
size_t sideband_output_size,
|
size_t sideband_output_size,
|
||||||
void* p_return_address) {
|
void* p_return_address) {
|
||||||
LOG_TRACE(Lib_Ajm, "called");
|
return BatchJobControlBufferRa(p_buffer, instance_id, flags, p_sideband_input,
|
||||||
|
sideband_input_size, p_sideband_output, sideband_output_size,
|
||||||
u8* p_current = (u8*)p_buffer;
|
p_return_address);
|
||||||
|
|
||||||
auto& header = AjmBufferExtract<AjmChunkHeader>(p_current);
|
|
||||||
header.ident = AjmIdentJob;
|
|
||||||
header.payload = instance_id;
|
|
||||||
|
|
||||||
const u8* const p_begin = p_current;
|
|
||||||
|
|
||||||
if (p_return_address != nullptr) {
|
|
||||||
auto& chunk_ra = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
chunk_ra.header.ident = AjmIdentReturnAddressBuf;
|
|
||||||
chunk_ra.header.payload = 0;
|
|
||||||
chunk_ra.header.size = 0;
|
|
||||||
chunk_ra.p_address = p_return_address;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto& chunk_input = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
chunk_input.header.ident = AjmIdentInputControlBuf;
|
|
||||||
chunk_input.header.payload = 0;
|
|
||||||
chunk_input.header.size = sideband_input_size;
|
|
||||||
chunk_input.p_address = p_sideband_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// 0x0000'0000'C001'8007 (AJM_INSTANCE_STATISTICS):
|
|
||||||
// | sideband | reserved | statistics | command | codec | revision |
|
|
||||||
// | 000 | 0000000000000 | 11000000000000011 | 0000 | 00000000 | 111 |
|
|
||||||
// statistics flags:
|
|
||||||
// STATISTICS_ENGINE | STATISTICS_ENGINE_PER_CODEC | ??STATISTICS_UNK?? | STATISTICS_MEMORY
|
|
||||||
|
|
||||||
// 0x0000'6000'0000'E7FF:
|
|
||||||
// | sideband | reserved | control | run | codec | revision |
|
|
||||||
// | 011 | 00000000000000000000000000000 | 111 | 00 | 11111111 | 111 |
|
|
||||||
const bool is_statistics = instance_id == AJM_INSTANCE_STATISTICS;
|
|
||||||
flags &= is_statistics ? 0x0000'0000'C001'8007 : 0x0000'6000'0000'E7FF;
|
|
||||||
|
|
||||||
auto& chunk_flags = AjmBufferExtract<AjmChunkHeader>(p_current);
|
|
||||||
chunk_flags.ident = AjmIdentControlFlags;
|
|
||||||
chunk_flags.payload = u32(flags >> 32);
|
|
||||||
chunk_flags.size = u32(flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto& chunk_output = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
chunk_output.header.ident = AjmIdentOutputControlBuf;
|
|
||||||
chunk_output.header.payload = 0;
|
|
||||||
chunk_output.header.size = sideband_output_size;
|
|
||||||
chunk_output.p_address = p_sideband_output;
|
|
||||||
}
|
|
||||||
|
|
||||||
header.size = u32(p_current - p_begin);
|
|
||||||
return p_current;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void* PS4_SYSV_ABI sceAjmBatchJobInlineBuffer(void* p_buffer, const void* p_data_input,
|
void* PS4_SYSV_ABI sceAjmBatchJobInlineBuffer(void* p_buffer, const void* p_data_input,
|
||||||
size_t data_input_size,
|
size_t data_input_size,
|
||||||
const void** pp_batch_address) {
|
const void** pp_batch_address) {
|
||||||
LOG_TRACE(Lib_Ajm, "called");
|
return BatchJobInlineBuffer(p_buffer, p_data_input, data_input_size, pp_batch_address);
|
||||||
|
|
||||||
u8* p_current = (u8*)p_buffer;
|
|
||||||
|
|
||||||
auto& header = AjmBufferExtract<AjmChunkHeader>(p_current);
|
|
||||||
header.ident = AjmIdentInlineBuf;
|
|
||||||
header.payload = 0;
|
|
||||||
header.size = Common::AlignUp(data_input_size, 8);
|
|
||||||
*pp_batch_address = p_current;
|
|
||||||
|
|
||||||
memcpy(p_current, p_data_input, data_input_size);
|
|
||||||
return p_current + header.size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(void* p_buffer, u32 instance_id, u64 flags,
|
void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(void* p_buffer, u32 instance_id, u64 flags,
|
||||||
@ -288,62 +46,9 @@ void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(void* p_buffer, u32 instance_id, u6
|
|||||||
void* p_data_output, size_t data_output_size,
|
void* p_data_output, size_t data_output_size,
|
||||||
void* p_sideband_output, size_t sideband_output_size,
|
void* p_sideband_output, size_t sideband_output_size,
|
||||||
void* p_return_address) {
|
void* p_return_address) {
|
||||||
LOG_TRACE(Lib_Ajm, "called");
|
return BatchJobRunBufferRa(p_buffer, instance_id, flags, p_data_input, data_input_size,
|
||||||
|
p_data_output, data_output_size, p_sideband_output,
|
||||||
u8* p_current = (u8*)p_buffer;
|
sideband_output_size, p_return_address);
|
||||||
|
|
||||||
auto& header = AjmBufferExtract<AjmChunkHeader>(p_current);
|
|
||||||
header.ident = AjmIdentJob;
|
|
||||||
header.payload = instance_id;
|
|
||||||
|
|
||||||
const u8* const p_begin = p_current;
|
|
||||||
|
|
||||||
if (p_return_address != nullptr) {
|
|
||||||
auto& chunk_ra = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
chunk_ra.header.ident = AjmIdentReturnAddressBuf;
|
|
||||||
chunk_ra.header.payload = 0;
|
|
||||||
chunk_ra.header.size = 0;
|
|
||||||
chunk_ra.p_address = p_return_address;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto& chunk_input = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
chunk_input.header.ident = AjmIdentInputRunBuf;
|
|
||||||
chunk_input.header.payload = 0;
|
|
||||||
chunk_input.header.size = data_input_size;
|
|
||||||
chunk_input.p_address = p_data_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// 0x0000'E000'0000'1FFF:
|
|
||||||
// | sideband | reserved | control | run | codec | revision |
|
|
||||||
// | 111 | 00000000000000000000000000000 | 000 | 11 | 11111111 | 111 |
|
|
||||||
flags &= 0x0000'E000'0000'1FFF;
|
|
||||||
|
|
||||||
auto& chunk_flags = AjmBufferExtract<AjmChunkHeader>(p_current);
|
|
||||||
chunk_flags.ident = AjmIdentRunFlags;
|
|
||||||
chunk_flags.payload = u32(flags >> 32);
|
|
||||||
chunk_flags.size = u32(flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto& chunk_output = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
chunk_output.header.ident = AjmIdentOutputRunBuf;
|
|
||||||
chunk_output.header.payload = 0;
|
|
||||||
chunk_output.header.size = data_output_size;
|
|
||||||
chunk_output.p_address = p_data_output;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto& chunk_output = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
chunk_output.header.ident = AjmIdentOutputControlBuf;
|
|
||||||
chunk_output.header.payload = 0;
|
|
||||||
chunk_output.header.size = sideband_output_size;
|
|
||||||
chunk_output.p_address = p_sideband_output;
|
|
||||||
}
|
|
||||||
|
|
||||||
header.size = u32(p_current - p_begin);
|
|
||||||
return p_current;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(
|
void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(
|
||||||
@ -351,280 +56,25 @@ void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(
|
|||||||
size_t num_data_input_buffers, const AjmBuffer* p_data_output_buffers,
|
size_t num_data_input_buffers, const AjmBuffer* p_data_output_buffers,
|
||||||
size_t num_data_output_buffers, void* p_sideband_output, size_t sideband_output_size,
|
size_t num_data_output_buffers, void* p_sideband_output, size_t sideband_output_size,
|
||||||
void* p_return_address) {
|
void* p_return_address) {
|
||||||
LOG_TRACE(Lib_Ajm, "called");
|
return BatchJobRunSplitBufferRa(p_buffer, instance_id, flags, p_data_input_buffers,
|
||||||
|
num_data_input_buffers, p_data_output_buffers,
|
||||||
u8* p_current = (u8*)p_buffer;
|
num_data_output_buffers, p_sideband_output,
|
||||||
|
sideband_output_size, p_return_address);
|
||||||
auto& header = AjmBufferExtract<AjmChunkHeader>(p_current);
|
|
||||||
header.ident = AjmIdentJob;
|
|
||||||
header.payload = instance_id;
|
|
||||||
|
|
||||||
const u8* const p_begin = p_current;
|
|
||||||
|
|
||||||
if (p_return_address != nullptr) {
|
|
||||||
auto& chunk_ra = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
chunk_ra.header.ident = AjmIdentReturnAddressBuf;
|
|
||||||
chunk_ra.header.payload = 0;
|
|
||||||
chunk_ra.header.size = 0;
|
|
||||||
chunk_ra.p_address = p_return_address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (s32 i = 0; i < num_data_input_buffers; i++) {
|
int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context_id, u8* p_batch, u32 batch_size,
|
||||||
auto& chunk_input = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
chunk_input.header.ident = AjmIdentInputRunBuf;
|
|
||||||
chunk_input.header.payload = 0;
|
|
||||||
chunk_input.header.size = p_data_input_buffers[i].size;
|
|
||||||
chunk_input.p_address = p_data_input_buffers[i].p_address;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// 0x0000'E000'0000'1FFF:
|
|
||||||
// | sideband | reserved | control | run | codec | revision |
|
|
||||||
// | 111 | 00000000000000000000000000000 | 000 | 11 | 11111111 | 111 |
|
|
||||||
flags &= 0x0000'E000'0000'1FFF;
|
|
||||||
|
|
||||||
auto& chunk_flags = AjmBufferExtract<AjmChunkHeader>(p_current);
|
|
||||||
chunk_flags.ident = AjmIdentRunFlags;
|
|
||||||
chunk_flags.payload = u32(flags >> 32);
|
|
||||||
chunk_flags.size = u32(flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (s32 i = 0; i < num_data_output_buffers; i++) {
|
|
||||||
auto& chunk_output = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
chunk_output.header.ident = AjmIdentOutputRunBuf;
|
|
||||||
chunk_output.header.payload = 0;
|
|
||||||
chunk_output.header.size = p_data_output_buffers[i].size;
|
|
||||||
chunk_output.p_address = p_data_output_buffers[i].p_address;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto& chunk_output = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
chunk_output.header.ident = AjmIdentOutputControlBuf;
|
|
||||||
chunk_output.header.payload = 0;
|
|
||||||
chunk_output.header.size = sideband_output_size;
|
|
||||||
chunk_output.p_address = p_sideband_output;
|
|
||||||
}
|
|
||||||
|
|
||||||
header.size = u32(p_current - p_begin);
|
|
||||||
return p_current;
|
|
||||||
}
|
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, u8* p_batch, u32 batch_size,
|
|
||||||
const int priority, AjmBatchError* batch_error,
|
const int priority, AjmBatchError* batch_error,
|
||||||
u32* out_batch_id) {
|
u32* out_batch_id) {
|
||||||
LOG_INFO(Lib_Ajm, "called context = {}, batch_size = {:#x}, priority = {}", context, batch_size,
|
LOG_TRACE(Lib_Ajm, "called context = {}, batch_size = {:#x}, priority = {}", context_id,
|
||||||
priority);
|
batch_size, priority);
|
||||||
|
return context->BatchStartBuffer(p_batch, batch_size, priority, batch_error, out_batch_id);
|
||||||
if ((batch_size & 7) != 0) {
|
|
||||||
LOG_ERROR(Lib_Ajm, "ORBIS_AJM_ERROR_MALFORMED_BATCH");
|
|
||||||
return ORBIS_AJM_ERROR_MALFORMED_BATCH;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto batch_info = std::make_shared<BatchInfo>();
|
int PS4_SYSV_ABI sceAjmBatchWait(const u32 context_id, const u32 batch_id, const u32 timeout,
|
||||||
auto batch_id = dev->batches.Create(batch_info);
|
|
||||||
if (!batch_id.has_value()) {
|
|
||||||
return ORBIS_AJM_ERROR_OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
batch_info->id = batch_id.value();
|
|
||||||
*out_batch_id = batch_id.value();
|
|
||||||
|
|
||||||
u8* p_current = p_batch;
|
|
||||||
u8* const p_batch_end = p_current + batch_size;
|
|
||||||
|
|
||||||
while (p_current < p_batch_end) {
|
|
||||||
auto& header = AjmBufferExtract<const AjmChunkHeader>(p_current);
|
|
||||||
ASSERT(header.ident == AjmIdentJob);
|
|
||||||
|
|
||||||
batch_info->jobs.push_back(AjmJob{});
|
|
||||||
auto& job = batch_info->jobs.back();
|
|
||||||
job.instance_id = header.payload;
|
|
||||||
|
|
||||||
std::optional<AjmJobFlags> job_flags = {};
|
|
||||||
std::optional<AjmChunkBuffer> input_control_buffer = {};
|
|
||||||
std::optional<AjmChunkBuffer> output_control_buffer = {};
|
|
||||||
std::optional<AjmChunkBuffer> inline_buffer = {};
|
|
||||||
boost::container::small_vector<AjmChunkBuffer, 16> input_run_buffers;
|
|
||||||
boost::container::small_vector<AjmChunkBuffer, 16> output_run_buffers;
|
|
||||||
|
|
||||||
// Read parameters of a job
|
|
||||||
auto* const p_job_end = p_current + header.size;
|
|
||||||
while (p_current < p_job_end) {
|
|
||||||
auto& header = AjmBufferPeek<AjmChunkHeader>(p_current);
|
|
||||||
switch (header.ident) {
|
|
||||||
case Identifier::AjmIdentInputRunBuf: {
|
|
||||||
auto& buffer = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
u8* p_begin = reinterpret_cast<u8*>(buffer.p_address);
|
|
||||||
job.input.buffer.insert(job.input.buffer.end(), p_begin,
|
|
||||||
p_begin + buffer.header.size);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Identifier::AjmIdentInputControlBuf: {
|
|
||||||
ASSERT_MSG(!input_control_buffer.has_value(),
|
|
||||||
"Only one instance of input control buffer is allowed per job");
|
|
||||||
input_control_buffer = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Identifier::AjmIdentControlFlags:
|
|
||||||
case Identifier::AjmIdentRunFlags: {
|
|
||||||
ASSERT_MSG(!job_flags.has_value(),
|
|
||||||
"Only one instance of job flags is allowed per job");
|
|
||||||
auto& flags_chunk = AjmBufferExtract<AjmChunkHeader>(p_current);
|
|
||||||
job_flags = AjmJobFlags{
|
|
||||||
.raw = (u64(flags_chunk.payload) << 32) + flags_chunk.size,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Identifier::AjmIdentReturnAddressBuf: {
|
|
||||||
// Ignore return address buffers.
|
|
||||||
AjmBufferSkip<AjmChunkBuffer>(p_current);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Identifier::AjmIdentInlineBuf: {
|
|
||||||
ASSERT_MSG(!output_control_buffer.has_value(),
|
|
||||||
"Only one instance of inline buffer is allowed per job");
|
|
||||||
inline_buffer = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Identifier::AjmIdentOutputRunBuf: {
|
|
||||||
auto& buffer = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
u8* p_begin = reinterpret_cast<u8*>(buffer.p_address);
|
|
||||||
job.output.buffers.emplace_back(
|
|
||||||
std::span<u8>(p_begin, p_begin + buffer.header.size));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Identifier::AjmIdentOutputControlBuf: {
|
|
||||||
ASSERT_MSG(!output_control_buffer.has_value(),
|
|
||||||
"Only one instance of output control buffer is allowed per job");
|
|
||||||
output_control_buffer = AjmBufferExtract<AjmChunkBuffer>(p_current);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
UNREACHABLE_MSG("Unknown chunk: {}", header.ident);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
job.flags = job_flags.value();
|
|
||||||
|
|
||||||
// Perform operation requested by control flags.
|
|
||||||
const auto control_flags = job_flags.value().control_flags;
|
|
||||||
if (True(control_flags & AjmJobControlFlags::Initialize)) {
|
|
||||||
ASSERT_MSG(input_control_buffer.has_value(),
|
|
||||||
"Initialize called without control buffer");
|
|
||||||
const auto& in_buffer = input_control_buffer.value();
|
|
||||||
job.input.init_params = AjmDecAt9InitializeParameters{};
|
|
||||||
std::memcpy(&job.input.init_params.value(), in_buffer.p_address, in_buffer.header.size);
|
|
||||||
}
|
|
||||||
if (True(control_flags & AjmJobControlFlags::Resample)) {
|
|
||||||
ASSERT_MSG(inline_buffer.has_value(),
|
|
||||||
"Resample paramters are stored in the inline buffer");
|
|
||||||
auto* p_buffer = reinterpret_cast<u8*>(inline_buffer.value().p_address);
|
|
||||||
job.input.resample_parameters =
|
|
||||||
AjmBufferExtract<AjmSidebandResampleParameters>(p_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize sideband input parameters
|
|
||||||
if (input_control_buffer.has_value()) {
|
|
||||||
auto* p_sideband = reinterpret_cast<u8*>(input_control_buffer.value().p_address);
|
|
||||||
auto* const p_end = p_sideband + input_control_buffer.value().header.size;
|
|
||||||
|
|
||||||
const auto sideband_flags = job_flags.value().sideband_flags;
|
|
||||||
if (True(sideband_flags & AjmJobSidebandFlags::Format) && p_sideband < p_end) {
|
|
||||||
job.input.format = AjmBufferExtract<AjmSidebandFormat>(p_sideband);
|
|
||||||
}
|
|
||||||
if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode) && p_sideband < p_end) {
|
|
||||||
job.input.gapless_decode = AjmBufferExtract<AjmSidebandGaplessDecode>(p_sideband);
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT_MSG(p_sideband <= p_end, "Input sideband out of bounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize sideband output parameters
|
|
||||||
if (output_control_buffer.has_value()) {
|
|
||||||
auto* p_sideband = reinterpret_cast<u8*>(output_control_buffer.value().p_address);
|
|
||||||
auto* const p_end = p_sideband + output_control_buffer.value().header.size;
|
|
||||||
job.output.p_result = &AjmBufferExtract<AjmSidebandResult>(p_sideband);
|
|
||||||
*job.output.p_result = AjmSidebandResult{};
|
|
||||||
|
|
||||||
const auto sideband_flags = job_flags.value().sideband_flags;
|
|
||||||
if (True(sideband_flags & AjmJobSidebandFlags::Stream) && p_sideband < p_end) {
|
|
||||||
job.output.p_stream = &AjmBufferExtract<AjmSidebandStream>(p_sideband);
|
|
||||||
*job.output.p_stream = AjmSidebandStream{};
|
|
||||||
}
|
|
||||||
if (True(sideband_flags & AjmJobSidebandFlags::Format) && p_sideband < p_end) {
|
|
||||||
LOG_ERROR(Lib_Ajm, "SIDEBAND_FORMAT is not implemented");
|
|
||||||
job.output.p_format = &AjmBufferExtract<AjmSidebandFormat>(p_sideband);
|
|
||||||
*job.output.p_format = AjmSidebandFormat{};
|
|
||||||
}
|
|
||||||
if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode) && p_sideband < p_end) {
|
|
||||||
job.output.p_gapless_decode =
|
|
||||||
&AjmBufferExtract<AjmSidebandGaplessDecode>(p_sideband);
|
|
||||||
*job.output.p_gapless_decode = AjmSidebandGaplessDecode{};
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto run_flags = job_flags.value().run_flags;
|
|
||||||
if (True(run_flags & AjmJobRunFlags::MultipleFrames) && p_sideband < p_end) {
|
|
||||||
job.output.p_mframe = &AjmBufferExtract<AjmSidebandMFrame>(p_sideband);
|
|
||||||
*job.output.p_mframe = AjmSidebandMFrame{};
|
|
||||||
}
|
|
||||||
if (True(run_flags & AjmJobRunFlags::GetCodecInfo) && p_sideband < p_end) {
|
|
||||||
job.output.p_codec_info = p_sideband;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT_MSG(p_sideband <= p_end, "Output sideband out of bounds");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard lock(dev->batch_queue_mutex);
|
|
||||||
dev->batch_queue.push(batch_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::unique_lock lock(dev->batch_queue_cv_mutex);
|
|
||||||
dev->batch_queue_cv.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchWait(const u32 context, const u32 batch_id, const u32 timeout,
|
|
||||||
AjmBatchError* const batch_error) {
|
AjmBatchError* const batch_error) {
|
||||||
LOG_INFO(Lib_Ajm, "called context = {}, batch_id = {}, timeout = {}", context, batch_id,
|
LOG_INFO(Lib_Ajm, "called context = {}, batch_id = {}, timeout = {}", context_id, batch_id,
|
||||||
timeout);
|
timeout);
|
||||||
|
return context->BatchWait(batch_id, timeout, batch_error);
|
||||||
std::shared_ptr<BatchInfo> batch{};
|
|
||||||
{
|
|
||||||
std::lock_guard guard(dev->batches_mutex);
|
|
||||||
const auto opt_batch = dev->batches.Get(batch_id);
|
|
||||||
if (!opt_batch.has_value()) {
|
|
||||||
return ORBIS_AJM_ERROR_INVALID_BATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
batch = opt_batch.value().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool expected = false;
|
|
||||||
if (!batch->waiting.compare_exchange_strong(expected, true)) {
|
|
||||||
return ORBIS_AJM_ERROR_BUSY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeout == ORBIS_AJM_WAIT_INFINITE) {
|
|
||||||
batch->finished.acquire();
|
|
||||||
} else if (!batch->finished.try_acquire_for(std::chrono::milliseconds(timeout))) {
|
|
||||||
batch->waiting = false;
|
|
||||||
return ORBIS_AJM_ERROR_IN_PROGRESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard guard(dev->batches_mutex);
|
|
||||||
dev->batches.Destroy(batch_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (batch->canceled) {
|
|
||||||
return ORBIS_AJM_ERROR_CANCELLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData() {
|
int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData() {
|
||||||
@ -649,13 +99,13 @@ int PS4_SYSV_ABI sceAjmFinalize() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* out_context) {
|
int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* p_context_id) {
|
||||||
LOG_INFO(Lib_Ajm, "called reserved = {}", reserved);
|
LOG_INFO(Lib_Ajm, "called reserved = {}", reserved);
|
||||||
if (out_context == nullptr || reserved != 0) {
|
if (p_context_id == nullptr || reserved != 0) {
|
||||||
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
*out_context = 1;
|
*p_context_id = 1;
|
||||||
dev = std::make_unique<AjmDevice>();
|
context = std::make_unique<AjmContext>();
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,61 +114,16 @@ int PS4_SYSV_ABI sceAjmInstanceCodecType() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmInstanceFlags flags,
|
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context_id, AjmCodecType codec_type,
|
||||||
u32* out_instance) {
|
AjmInstanceFlags flags, u32* out_instance) {
|
||||||
LOG_INFO(Lib_Ajm, "called context = {}, codec_type = {}, flags = {:#x}", context,
|
LOG_INFO(Lib_Ajm, "called context = {}, codec_type = {}, flags = {:#x}", context_id,
|
||||||
magic_enum::enum_name(codec_type), flags.raw);
|
magic_enum::enum_name(codec_type), flags.raw);
|
||||||
|
return context->InstanceCreate(codec_type, flags, out_instance);
|
||||||
if (codec_type >= AjmCodecType::Max) {
|
|
||||||
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
|
||||||
}
|
|
||||||
if (flags.version == 0) {
|
|
||||||
return ORBIS_AJM_ERROR_WRONG_REVISION_FLAG;
|
|
||||||
}
|
|
||||||
if (!dev->IsRegistered(codec_type)) {
|
|
||||||
return ORBIS_AJM_ERROR_CODEC_NOT_REGISTERED;
|
|
||||||
}
|
|
||||||
if (dev->curr_cursor == dev->release_cursor) {
|
|
||||||
return ORBIS_AJM_ERROR_OUT_OF_RESOURCES;
|
|
||||||
}
|
|
||||||
ASSERT_MSG(flags.format == 0, "Only signed 16-bit PCM output is supported currently!");
|
|
||||||
const u32 index = dev->free_instances[dev->curr_cursor++];
|
|
||||||
dev->curr_cursor %= MaxInstances;
|
|
||||||
std::unique_ptr<AjmInstance> instance;
|
|
||||||
switch (codec_type) {
|
|
||||||
case AjmCodecType::Mp3Dec:
|
|
||||||
instance = std::make_unique<AjmMp3Decoder>();
|
|
||||||
break;
|
|
||||||
case AjmCodecType::At9Dec:
|
|
||||||
instance = std::make_unique<AjmAt9Decoder>();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
UNREACHABLE_MSG("Codec #{} not implemented", u32(codec_type));
|
|
||||||
}
|
|
||||||
instance->index = index;
|
|
||||||
instance->codec_type = codec_type;
|
|
||||||
instance->num_channels = flags.channels;
|
|
||||||
instance->flags = flags;
|
|
||||||
dev->instances[index] = std::move(instance);
|
|
||||||
*out_instance = index;
|
|
||||||
|
|
||||||
LOG_INFO(Lib_Ajm, "instance = {}", index);
|
|
||||||
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context, u32 instance) {
|
int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context_id, u32 instance_id) {
|
||||||
LOG_INFO(Lib_Ajm, "called context = {}, instance = {}", context, instance);
|
LOG_INFO(Lib_Ajm, "called context = {}, instance = {}", context_id, instance_id);
|
||||||
if ((instance & 0x3fff) > MaxInstances) {
|
return context->InstanceDestroy(instance_id);
|
||||||
return ORBIS_AJM_ERROR_INVALID_INSTANCE;
|
|
||||||
}
|
|
||||||
const u32 next_slot = (dev->release_cursor + 1) % MaxInstances;
|
|
||||||
if (next_slot != dev->curr_cursor) {
|
|
||||||
dev->free_instances[dev->release_cursor] = instance;
|
|
||||||
dev->release_cursor = next_slot;
|
|
||||||
}
|
|
||||||
dev->instances[instance].reset();
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmInstanceExtend() {
|
int PS4_SYSV_ABI sceAjmInstanceExtend() {
|
||||||
@ -741,17 +146,12 @@ int PS4_SYSV_ABI sceAjmMemoryUnregister() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmModuleRegister(u32 context, AjmCodecType codec_type, s64 reserved) {
|
int PS4_SYSV_ABI sceAjmModuleRegister(u32 context_id, AjmCodecType codec_type, s64 reserved) {
|
||||||
LOG_INFO(Lib_Ajm, "called context = {}, codec_type = {}, reserved = {}", context,
|
LOG_INFO(Lib_Ajm, "called context = {}, codec_type = {}", context_id, u32(codec_type));
|
||||||
u32(codec_type), reserved);
|
if (reserved != 0) {
|
||||||
if (codec_type >= AjmCodecType::Max || reserved != 0) {
|
|
||||||
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
if (dev->IsRegistered(codec_type)) {
|
return context->ModuleRegister(codec_type);
|
||||||
return ORBIS_AJM_ERROR_CODEC_ALREADY_REGISTERED;
|
|
||||||
}
|
|
||||||
dev->Register(codec_type);
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmModuleUnregister() {
|
int PS4_SYSV_ABI sceAjmModuleUnregister() {
|
||||||
|
@ -7,14 +7,15 @@
|
|||||||
#include "common/enum.h"
|
#include "common/enum.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
#include "core/libraries/ajm/ajm_instance.h"
|
|
||||||
|
|
||||||
namespace Core::Loader {
|
namespace Core::Loader {
|
||||||
class SymbolsResolver;
|
class SymbolsResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Libraries::Ajm {
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
constexpr u32 ORBIS_AT9_CONFIG_DATA_SIZE = 4;
|
||||||
|
constexpr u32 AJM_INSTANCE_STATISTICS = 0x80000;
|
||||||
|
|
||||||
struct AjmBatchInfo {
|
struct AjmBatchInfo {
|
||||||
void* pBuffer;
|
void* pBuffer;
|
||||||
u64 offset;
|
u64 offset;
|
||||||
@ -33,30 +34,6 @@ struct AjmBuffer {
|
|||||||
u64 size;
|
u64 size;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Identifier : u8 {
|
|
||||||
AjmIdentJob = 0,
|
|
||||||
AjmIdentInputRunBuf = 1,
|
|
||||||
AjmIdentInputControlBuf = 2,
|
|
||||||
AjmIdentControlFlags = 3,
|
|
||||||
AjmIdentRunFlags = 4,
|
|
||||||
AjmIdentReturnAddressBuf = 6,
|
|
||||||
AjmIdentInlineBuf = 7,
|
|
||||||
AjmIdentOutputRunBuf = 17,
|
|
||||||
AjmIdentOutputControlBuf = 18,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmChunkHeader {
|
|
||||||
u32 ident : 6;
|
|
||||||
u32 payload : 20;
|
|
||||||
u32 reserved : 6;
|
|
||||||
u32 size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmChunkBuffer {
|
|
||||||
AjmChunkHeader header;
|
|
||||||
void* p_address;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class AjmJobControlFlags : u64 {
|
enum class AjmJobControlFlags : u64 {
|
||||||
Reset = 1 << 0,
|
Reset = 1 << 0,
|
||||||
Initialize = 1 << 1,
|
Initialize = 1 << 1,
|
||||||
@ -89,6 +66,65 @@ union AjmJobFlags {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AjmSidebandResult {
|
||||||
|
s32 result;
|
||||||
|
s32 internal_result;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmSidebandMFrame {
|
||||||
|
u32 num_frames;
|
||||||
|
u32 reserved;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmSidebandStream {
|
||||||
|
s32 input_consumed;
|
||||||
|
s32 output_written;
|
||||||
|
u64 total_decoded_samples;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AjmFormatEncoding : u32 {
|
||||||
|
S16 = 0,
|
||||||
|
S32 = 1,
|
||||||
|
Float = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmSidebandFormat {
|
||||||
|
u32 num_channels;
|
||||||
|
u32 channel_mask;
|
||||||
|
u32 sampl_freq;
|
||||||
|
AjmFormatEncoding sample_encoding;
|
||||||
|
u32 bitrate;
|
||||||
|
u32 reserved;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmSidebandGaplessDecode {
|
||||||
|
u32 total_samples;
|
||||||
|
u16 skip_samples;
|
||||||
|
u16 skipped_samples;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmSidebandResampleParameters {
|
||||||
|
float ratio;
|
||||||
|
uint32_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmDecAt9InitializeParameters {
|
||||||
|
u8 config_data[ORBIS_AT9_CONFIG_DATA_SIZE];
|
||||||
|
u32 reserved;
|
||||||
|
};
|
||||||
|
|
||||||
|
union AjmInstanceFlags {
|
||||||
|
u64 raw;
|
||||||
|
struct {
|
||||||
|
u64 version : 3;
|
||||||
|
u64 channels : 4;
|
||||||
|
u64 format : 3;
|
||||||
|
u64 gapless_loop : 1;
|
||||||
|
u64 : 21;
|
||||||
|
u64 codec : 28;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
struct AjmDecMp3ParseFrame;
|
struct AjmDecMp3ParseFrame;
|
||||||
enum class AjmCodecType : u32;
|
enum class AjmCodecType : u32;
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ void AjmAt9Decoder::GetCodecInfo(void* out_info) {
|
|||||||
codec_info->uiSuperFrameSize = decoder_codec_info.superframeSize;
|
codec_info->uiSuperFrameSize = decoder_codec_info.superframeSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AjmAt9Decoder::Decode(const AjmJobInput* input, AjmJobOutput* output) {
|
void AjmAt9Decoder::Decode(const AjmJob::Input* input, AjmJob::Output* output) {
|
||||||
Atrac9CodecInfo codec_info;
|
Atrac9CodecInfo codec_info;
|
||||||
Atrac9GetCodecInfo(handle, &codec_info);
|
Atrac9GetCodecInfo(handle, &codec_info);
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ struct AjmAt9Decoder final : AjmInstance {
|
|||||||
return sizeof(AjmSidebandDecAt9CodecInfo);
|
return sizeof(AjmSidebandDecAt9CodecInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Decode(const AjmJobInput* input, AjmJobOutput* output) override;
|
void Decode(const AjmJob::Input* input, AjmJob::Output* output) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ResetCodec();
|
void ResetCodec();
|
||||||
|
461
src/core/libraries/ajm/ajm_batch.cpp
Normal file
461
src/core/libraries/ajm/ajm_batch.cpp
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/alignment.h"
|
||||||
|
#include "core/libraries/ajm/ajm_batch.h"
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
enum Identifier : u8 {
|
||||||
|
AjmIdentJob = 0,
|
||||||
|
AjmIdentInputRunBuf = 1,
|
||||||
|
AjmIdentInputControlBuf = 2,
|
||||||
|
AjmIdentControlFlags = 3,
|
||||||
|
AjmIdentRunFlags = 4,
|
||||||
|
AjmIdentReturnAddressBuf = 6,
|
||||||
|
AjmIdentInlineBuf = 7,
|
||||||
|
AjmIdentOutputRunBuf = 17,
|
||||||
|
AjmIdentOutputControlBuf = 18,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmChunkHeader {
|
||||||
|
u32 ident : 6;
|
||||||
|
u32 payload : 20;
|
||||||
|
u32 reserved : 6;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(AjmChunkHeader) == 4);
|
||||||
|
|
||||||
|
struct AjmChunkJob {
|
||||||
|
AjmChunkHeader header;
|
||||||
|
u32 size;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(AjmChunkJob) == 8);
|
||||||
|
|
||||||
|
struct AjmChunkFlags {
|
||||||
|
AjmChunkHeader header;
|
||||||
|
u32 flags_low;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(AjmChunkFlags) == 8);
|
||||||
|
|
||||||
|
struct AjmChunkBuffer {
|
||||||
|
AjmChunkHeader header;
|
||||||
|
u32 size;
|
||||||
|
void* p_address;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(AjmChunkBuffer) == 16);
|
||||||
|
|
||||||
|
class AjmBatchBuffer {
|
||||||
|
public:
|
||||||
|
static constexpr size_t DynamicExtent = std::numeric_limits<size_t>::max();
|
||||||
|
|
||||||
|
AjmBatchBuffer(u8* begin, u8* end)
|
||||||
|
: m_p_begin(begin), m_p_current(begin), m_size(end - begin) {}
|
||||||
|
AjmBatchBuffer(u8* begin, size_t size = DynamicExtent)
|
||||||
|
: m_p_begin(begin), m_p_current(m_p_begin), m_size(size) {}
|
||||||
|
AjmBatchBuffer(std::span<u8> data)
|
||||||
|
: m_p_begin(data.data()), m_p_current(m_p_begin), m_size(data.size()) {}
|
||||||
|
|
||||||
|
AjmBatchBuffer SubBuffer(size_t size = DynamicExtent) {
|
||||||
|
auto current = m_p_current;
|
||||||
|
if (size != DynamicExtent) {
|
||||||
|
m_p_current += size;
|
||||||
|
}
|
||||||
|
return AjmBatchBuffer(current, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T& Peek() const {
|
||||||
|
DEBUG_ASSERT(m_size == DynamicExtent || (m_p_current + sizeof(T)) <= (m_p_begin + m_size));
|
||||||
|
return *reinterpret_cast<T*>(m_p_current);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T& Consume() {
|
||||||
|
auto* const result = reinterpret_cast<T*>(m_p_current);
|
||||||
|
m_p_current += sizeof(T);
|
||||||
|
DEBUG_ASSERT(m_size == DynamicExtent || m_p_current <= (m_p_begin + m_size));
|
||||||
|
return *result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void Skip() {
|
||||||
|
Advance(sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Advance(size_t size) {
|
||||||
|
m_p_current += size;
|
||||||
|
DEBUG_ASSERT(m_size == DynamicExtent || m_p_current <= (m_p_begin + m_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsEmpty() {
|
||||||
|
return m_size != DynamicExtent && m_p_current >= (m_p_begin + m_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BytesConsumed() const {
|
||||||
|
return m_p_current - m_p_begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BytesRemaining() const {
|
||||||
|
if (m_size == DynamicExtent) {
|
||||||
|
return DynamicExtent;
|
||||||
|
}
|
||||||
|
return m_size - (m_p_current - m_p_begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8* GetCurrent() const {
|
||||||
|
return m_p_current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
u8* m_p_begin{};
|
||||||
|
u8* m_p_current{};
|
||||||
|
size_t m_size{};
|
||||||
|
};
|
||||||
|
|
||||||
|
AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
|
||||||
|
std::optional<AjmJobFlags> job_flags = {};
|
||||||
|
std::optional<AjmChunkBuffer> input_control_buffer = {};
|
||||||
|
std::optional<AjmChunkBuffer> output_control_buffer = {};
|
||||||
|
std::optional<AjmChunkBuffer> inline_buffer = {};
|
||||||
|
|
||||||
|
AjmJob job;
|
||||||
|
job.instance_id = instance_id;
|
||||||
|
|
||||||
|
// Read parameters of a job
|
||||||
|
while (!batch_buffer.IsEmpty()) {
|
||||||
|
auto& header = batch_buffer.Peek<AjmChunkHeader>();
|
||||||
|
switch (header.ident) {
|
||||||
|
case Identifier::AjmIdentInputRunBuf: {
|
||||||
|
auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
u8* p_begin = reinterpret_cast<u8*>(buffer.p_address);
|
||||||
|
job.input.buffer.insert(job.input.buffer.end(), p_begin, p_begin + buffer.size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Identifier::AjmIdentInputControlBuf: {
|
||||||
|
ASSERT_MSG(!input_control_buffer.has_value(),
|
||||||
|
"Only one instance of input control buffer is allowed per job");
|
||||||
|
input_control_buffer = batch_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Identifier::AjmIdentControlFlags:
|
||||||
|
case Identifier::AjmIdentRunFlags: {
|
||||||
|
ASSERT_MSG(!job_flags.has_value(), "Only one instance of job flags is allowed per job");
|
||||||
|
auto& chunk = batch_buffer.Consume<AjmChunkFlags>();
|
||||||
|
job_flags = AjmJobFlags{
|
||||||
|
.raw = (u64(chunk.header.payload) << 32) + chunk.flags_low,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Identifier::AjmIdentReturnAddressBuf: {
|
||||||
|
// Ignore return address buffers.
|
||||||
|
batch_buffer.Skip<AjmChunkBuffer>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Identifier::AjmIdentInlineBuf: {
|
||||||
|
ASSERT_MSG(!output_control_buffer.has_value(),
|
||||||
|
"Only one instance of inline buffer is allowed per job");
|
||||||
|
inline_buffer = batch_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Identifier::AjmIdentOutputRunBuf: {
|
||||||
|
auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
u8* p_begin = reinterpret_cast<u8*>(buffer.p_address);
|
||||||
|
job.output.buffers.emplace_back(std::span<u8>(p_begin, p_begin + buffer.size));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Identifier::AjmIdentOutputControlBuf: {
|
||||||
|
ASSERT_MSG(!output_control_buffer.has_value(),
|
||||||
|
"Only one instance of output control buffer is allowed per job");
|
||||||
|
output_control_buffer = batch_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Unknown chunk: {}", header.ident);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
job.flags = job_flags.value();
|
||||||
|
|
||||||
|
// Initialize sideband input parameters
|
||||||
|
if (input_control_buffer.has_value()) {
|
||||||
|
AjmBatchBuffer input_batch(reinterpret_cast<u8*>(input_control_buffer->p_address),
|
||||||
|
input_control_buffer->size);
|
||||||
|
|
||||||
|
const auto sideband_flags = job_flags->sideband_flags;
|
||||||
|
if (True(sideband_flags & AjmJobSidebandFlags::Format) && !input_batch.IsEmpty()) {
|
||||||
|
job.input.format = input_batch.Consume<AjmSidebandFormat>();
|
||||||
|
}
|
||||||
|
if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode) && !input_batch.IsEmpty()) {
|
||||||
|
job.input.gapless_decode = input_batch.Consume<AjmSidebandGaplessDecode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto control_flags = job_flags.value().control_flags;
|
||||||
|
if (True(control_flags & AjmJobControlFlags::Initialize)) {
|
||||||
|
job.input.init_params = AjmDecAt9InitializeParameters{};
|
||||||
|
std::memcpy(&job.input.init_params.value(), input_batch.GetCurrent(),
|
||||||
|
input_batch.BytesRemaining());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inline_buffer.has_value()) {
|
||||||
|
AjmBatchBuffer inline_batch(reinterpret_cast<u8*>(inline_buffer->p_address),
|
||||||
|
inline_buffer->size);
|
||||||
|
|
||||||
|
const auto control_flags = job_flags.value().control_flags;
|
||||||
|
if (True(control_flags & AjmJobControlFlags::Resample)) {
|
||||||
|
job.input.resample_parameters = inline_batch.Consume<AjmSidebandResampleParameters>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize sideband output parameters
|
||||||
|
if (output_control_buffer.has_value()) {
|
||||||
|
AjmBatchBuffer output_batch(reinterpret_cast<u8*>(output_control_buffer->p_address),
|
||||||
|
output_control_buffer->size);
|
||||||
|
|
||||||
|
job.output.p_result = &output_batch.Consume<AjmSidebandResult>();
|
||||||
|
*job.output.p_result = AjmSidebandResult{};
|
||||||
|
|
||||||
|
const auto sideband_flags = job_flags->sideband_flags;
|
||||||
|
if (True(sideband_flags & AjmJobSidebandFlags::Stream) && !output_batch.IsEmpty()) {
|
||||||
|
job.output.p_stream = &output_batch.Consume<AjmSidebandStream>();
|
||||||
|
*job.output.p_stream = AjmSidebandStream{};
|
||||||
|
}
|
||||||
|
if (True(sideband_flags & AjmJobSidebandFlags::Format) && !output_batch.IsEmpty()) {
|
||||||
|
LOG_ERROR(Lib_Ajm, "SIDEBAND_FORMAT is not implemented");
|
||||||
|
job.output.p_format = &output_batch.Consume<AjmSidebandFormat>();
|
||||||
|
*job.output.p_format = AjmSidebandFormat{};
|
||||||
|
}
|
||||||
|
if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode) && !output_batch.IsEmpty()) {
|
||||||
|
job.output.p_gapless_decode = &output_batch.Consume<AjmSidebandGaplessDecode>();
|
||||||
|
*job.output.p_gapless_decode = AjmSidebandGaplessDecode{};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto run_flags = job_flags->run_flags;
|
||||||
|
if (True(run_flags & AjmJobRunFlags::MultipleFrames) && !output_batch.IsEmpty()) {
|
||||||
|
job.output.p_mframe = &output_batch.Consume<AjmSidebandMFrame>();
|
||||||
|
*job.output.p_mframe = AjmSidebandMFrame{};
|
||||||
|
}
|
||||||
|
if (True(run_flags & AjmJobRunFlags::GetCodecInfo) && !output_batch.IsEmpty()) {
|
||||||
|
job.output.p_codec_info = output_batch.GetCurrent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AjmBatch> AjmBatch::FromBatchBuffer(std::span<u8> data) {
|
||||||
|
auto batch = std::make_shared<AjmBatch>();
|
||||||
|
|
||||||
|
AjmBatchBuffer buffer(data);
|
||||||
|
while (!buffer.IsEmpty()) {
|
||||||
|
auto& job_chunk = buffer.Consume<AjmChunkJob>();
|
||||||
|
ASSERT(job_chunk.header.ident == AjmIdentJob);
|
||||||
|
auto instance_id = job_chunk.header.payload;
|
||||||
|
batch->jobs.push_back(AjmJobFromBatchBuffer(instance_id, buffer.SubBuffer(job_chunk.size)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return batch;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* BatchJobControlBufferRa(void* p_buffer, u32 instance_id, u64 flags, void* p_sideband_input,
|
||||||
|
size_t sideband_input_size, void* p_sideband_output,
|
||||||
|
size_t sideband_output_size, void* p_return_address) {
|
||||||
|
LOG_TRACE(Lib_Ajm, "called");
|
||||||
|
|
||||||
|
AjmBatchBuffer buffer(reinterpret_cast<u8*>(p_buffer));
|
||||||
|
|
||||||
|
auto& job_chunk = buffer.Consume<AjmChunkJob>();
|
||||||
|
job_chunk.header.ident = AjmIdentJob;
|
||||||
|
job_chunk.header.payload = instance_id;
|
||||||
|
|
||||||
|
auto job_buffer = buffer.SubBuffer();
|
||||||
|
|
||||||
|
if (p_return_address != nullptr) {
|
||||||
|
auto& chunk_ra = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
chunk_ra.header.ident = AjmIdentReturnAddressBuf;
|
||||||
|
chunk_ra.header.payload = 0;
|
||||||
|
chunk_ra.size = 0;
|
||||||
|
chunk_ra.p_address = p_return_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto& chunk_input = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
chunk_input.header.ident = AjmIdentInputControlBuf;
|
||||||
|
chunk_input.header.payload = 0;
|
||||||
|
chunk_input.size = sideband_input_size;
|
||||||
|
chunk_input.p_address = p_sideband_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// 0x0000'0000'C001'8007 (AJM_INSTANCE_STATISTICS):
|
||||||
|
// | sideband | reserved | statistics | command | codec | revision |
|
||||||
|
// | 000 | 0000000000000 | 11000000000000011 | 0000 | 00000000 | 111 |
|
||||||
|
// statistics flags:
|
||||||
|
// STATISTICS_ENGINE | STATISTICS_ENGINE_PER_CODEC | ??STATISTICS_UNK?? | STATISTICS_MEMORY
|
||||||
|
|
||||||
|
// 0x0000'6000'0000'E7FF:
|
||||||
|
// | sideband | reserved | control | run | codec | revision |
|
||||||
|
// | 011 | 00000000000000000000000000000 | 111 | 00 | 11111111 | 111 |
|
||||||
|
const bool is_statistics = instance_id == AJM_INSTANCE_STATISTICS;
|
||||||
|
flags &= is_statistics ? 0x0000'0000'C001'8007 : 0x0000'6000'0000'E7FF;
|
||||||
|
|
||||||
|
auto& chunk_flags = job_buffer.Consume<AjmChunkFlags>();
|
||||||
|
chunk_flags.header.ident = AjmIdentControlFlags;
|
||||||
|
chunk_flags.header.payload = u32(flags >> 32);
|
||||||
|
chunk_flags.flags_low = u32(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto& chunk_output = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
chunk_output.header.ident = AjmIdentOutputControlBuf;
|
||||||
|
chunk_output.header.payload = 0;
|
||||||
|
chunk_output.size = sideband_output_size;
|
||||||
|
chunk_output.p_address = p_sideband_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
job_chunk.size = job_buffer.BytesConsumed();
|
||||||
|
return job_buffer.GetCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void* BatchJobInlineBuffer(void* p_buffer, const void* p_data_input, size_t data_input_size,
|
||||||
|
const void** pp_batch_address) {
|
||||||
|
LOG_TRACE(Lib_Ajm, "called");
|
||||||
|
|
||||||
|
AjmBatchBuffer buffer(reinterpret_cast<u8*>(p_buffer));
|
||||||
|
|
||||||
|
auto& job_chunk = buffer.Consume<AjmChunkJob>();
|
||||||
|
job_chunk.header.ident = AjmIdentInlineBuf;
|
||||||
|
job_chunk.header.payload = 0;
|
||||||
|
job_chunk.size = Common::AlignUp(data_input_size, 8);
|
||||||
|
*pp_batch_address = buffer.GetCurrent();
|
||||||
|
|
||||||
|
memcpy(buffer.GetCurrent(), p_data_input, data_input_size);
|
||||||
|
return buffer.GetCurrent() + job_chunk.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* BatchJobRunBufferRa(void* p_buffer, u32 instance_id, u64 flags, void* p_data_input,
|
||||||
|
size_t data_input_size, void* p_data_output, size_t data_output_size,
|
||||||
|
void* p_sideband_output, size_t sideband_output_size,
|
||||||
|
void* p_return_address) {
|
||||||
|
LOG_TRACE(Lib_Ajm, "called");
|
||||||
|
|
||||||
|
AjmBatchBuffer buffer(reinterpret_cast<u8*>(p_buffer));
|
||||||
|
|
||||||
|
auto& job_chunk = buffer.Consume<AjmChunkJob>();
|
||||||
|
job_chunk.header.ident = AjmIdentJob;
|
||||||
|
job_chunk.header.payload = instance_id;
|
||||||
|
|
||||||
|
auto job_buffer = buffer.SubBuffer();
|
||||||
|
|
||||||
|
if (p_return_address != nullptr) {
|
||||||
|
auto& chunk_ra = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
chunk_ra.header.ident = AjmIdentReturnAddressBuf;
|
||||||
|
chunk_ra.header.payload = 0;
|
||||||
|
chunk_ra.size = 0;
|
||||||
|
chunk_ra.p_address = p_return_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto& chunk_input = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
chunk_input.header.ident = AjmIdentInputRunBuf;
|
||||||
|
chunk_input.header.payload = 0;
|
||||||
|
chunk_input.size = data_input_size;
|
||||||
|
chunk_input.p_address = p_data_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// 0x0000'E000'0000'1FFF:
|
||||||
|
// | sideband | reserved | control | run | codec | revision |
|
||||||
|
// | 111 | 00000000000000000000000000000 | 000 | 11 | 11111111 | 111 |
|
||||||
|
flags &= 0x0000'E000'0000'1FFF;
|
||||||
|
|
||||||
|
auto& chunk_flags = job_buffer.Consume<AjmChunkFlags>();
|
||||||
|
chunk_flags.header.ident = AjmIdentRunFlags;
|
||||||
|
chunk_flags.header.payload = u32(flags >> 32);
|
||||||
|
chunk_flags.flags_low = u32(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto& chunk_output = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
chunk_output.header.ident = AjmIdentOutputRunBuf;
|
||||||
|
chunk_output.header.payload = 0;
|
||||||
|
chunk_output.size = data_output_size;
|
||||||
|
chunk_output.p_address = p_data_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto& chunk_output = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
chunk_output.header.ident = AjmIdentOutputControlBuf;
|
||||||
|
chunk_output.header.payload = 0;
|
||||||
|
chunk_output.size = sideband_output_size;
|
||||||
|
chunk_output.p_address = p_sideband_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
job_chunk.size = job_buffer.BytesConsumed();
|
||||||
|
return job_buffer.GetCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void* BatchJobRunSplitBufferRa(void* p_buffer, u32 instance_id, u64 flags,
|
||||||
|
const AjmBuffer* p_data_input_buffers, size_t num_data_input_buffers,
|
||||||
|
const AjmBuffer* p_data_output_buffers,
|
||||||
|
size_t num_data_output_buffers, void* p_sideband_output,
|
||||||
|
size_t sideband_output_size, void* p_return_address) {
|
||||||
|
LOG_TRACE(Lib_Ajm, "called");
|
||||||
|
|
||||||
|
AjmBatchBuffer buffer(reinterpret_cast<u8*>(p_buffer));
|
||||||
|
|
||||||
|
auto& job_chunk = buffer.Consume<AjmChunkJob>();
|
||||||
|
job_chunk.header.ident = AjmIdentJob;
|
||||||
|
job_chunk.header.payload = instance_id;
|
||||||
|
|
||||||
|
auto job_buffer = buffer.SubBuffer();
|
||||||
|
|
||||||
|
if (p_return_address != nullptr) {
|
||||||
|
auto& chunk_ra = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
chunk_ra.header.ident = AjmIdentReturnAddressBuf;
|
||||||
|
chunk_ra.header.payload = 0;
|
||||||
|
chunk_ra.size = 0;
|
||||||
|
chunk_ra.p_address = p_return_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (s32 i = 0; i < num_data_input_buffers; i++) {
|
||||||
|
auto& chunk_input = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
chunk_input.header.ident = AjmIdentInputRunBuf;
|
||||||
|
chunk_input.header.payload = 0;
|
||||||
|
chunk_input.size = p_data_input_buffers[i].size;
|
||||||
|
chunk_input.p_address = p_data_input_buffers[i].p_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// 0x0000'E000'0000'1FFF:
|
||||||
|
// | sideband | reserved | control | run | codec | revision |
|
||||||
|
// | 111 | 00000000000000000000000000000 | 000 | 11 | 11111111 | 111 |
|
||||||
|
flags &= 0x0000'E000'0000'1FFF;
|
||||||
|
|
||||||
|
auto& chunk_flags = job_buffer.Consume<AjmChunkFlags>();
|
||||||
|
chunk_flags.header.ident = AjmIdentRunFlags;
|
||||||
|
chunk_flags.header.payload = u32(flags >> 32);
|
||||||
|
chunk_flags.flags_low = u32(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (s32 i = 0; i < num_data_output_buffers; i++) {
|
||||||
|
auto& chunk_output = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
chunk_output.header.ident = AjmIdentOutputRunBuf;
|
||||||
|
chunk_output.header.payload = 0;
|
||||||
|
chunk_output.size = p_data_output_buffers[i].size;
|
||||||
|
chunk_output.p_address = p_data_output_buffers[i].p_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto& chunk_output = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
chunk_output.header.ident = AjmIdentOutputControlBuf;
|
||||||
|
chunk_output.header.payload = 0;
|
||||||
|
chunk_output.size = sideband_output_size;
|
||||||
|
chunk_output.p_address = p_sideband_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
job_chunk.size = job_buffer.BytesConsumed();
|
||||||
|
return job_buffer.GetCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Libraries::Ajm
|
74
src/core/libraries/ajm/ajm_batch.h
Normal file
74
src/core/libraries/ajm/ajm_batch.h
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "core/libraries/ajm/ajm.h"
|
||||||
|
|
||||||
|
#include <boost/container/small_vector.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <semaphore>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
struct AjmJob {
|
||||||
|
struct Input {
|
||||||
|
std::optional<AjmDecAt9InitializeParameters> init_params;
|
||||||
|
std::optional<AjmSidebandResampleParameters> resample_parameters;
|
||||||
|
std::optional<AjmSidebandFormat> format;
|
||||||
|
std::optional<AjmSidebandGaplessDecode> gapless_decode;
|
||||||
|
std::vector<u8> buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Output {
|
||||||
|
boost::container::small_vector<std::span<u8>, 4> buffers;
|
||||||
|
AjmSidebandResult* p_result = nullptr;
|
||||||
|
AjmSidebandStream* p_stream = nullptr;
|
||||||
|
AjmSidebandFormat* p_format = nullptr;
|
||||||
|
AjmSidebandGaplessDecode* p_gapless_decode = nullptr;
|
||||||
|
AjmSidebandMFrame* p_mframe = nullptr;
|
||||||
|
u8* p_codec_info = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
u32 instance_id{};
|
||||||
|
AjmJobFlags flags{};
|
||||||
|
Input input;
|
||||||
|
Output output;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmBatch {
|
||||||
|
u32 id{};
|
||||||
|
std::atomic_bool waiting{};
|
||||||
|
std::atomic_bool canceled{};
|
||||||
|
std::binary_semaphore finished{0};
|
||||||
|
boost::container::small_vector<AjmJob, 8> jobs;
|
||||||
|
|
||||||
|
static std::shared_ptr<AjmBatch> FromBatchBuffer(std::span<u8> buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
void* BatchJobControlBufferRa(void* p_buffer, u32 instance_id, u64 flags, void* p_sideband_input,
|
||||||
|
size_t sideband_input_size, void* p_sideband_output,
|
||||||
|
size_t sideband_output_size, void* p_return_address);
|
||||||
|
|
||||||
|
void* BatchJobInlineBuffer(void* p_buffer, const void* p_data_input, size_t data_input_size,
|
||||||
|
const void** pp_batch_address);
|
||||||
|
|
||||||
|
void* BatchJobRunBufferRa(void* p_buffer, u32 instance_id, u64 flags, void* p_data_input,
|
||||||
|
size_t data_input_size, void* p_data_output, size_t data_output_size,
|
||||||
|
void* p_sideband_output, size_t sideband_output_size,
|
||||||
|
void* p_return_address);
|
||||||
|
|
||||||
|
void* BatchJobRunSplitBufferRa(void* p_buffer, u32 instance_id, u64 flags,
|
||||||
|
const AjmBuffer* p_data_input_buffers, size_t num_data_input_buffers,
|
||||||
|
const AjmBuffer* p_data_output_buffers,
|
||||||
|
size_t num_data_output_buffers, void* p_sideband_output,
|
||||||
|
size_t sideband_output_size, void* p_return_address);
|
||||||
|
|
||||||
|
} // namespace Libraries::Ajm
|
230
src/core/libraries/ajm/ajm_context.cpp
Normal file
230
src/core/libraries/ajm/ajm_context.cpp
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "core/libraries/ajm/ajm_at9.h"
|
||||||
|
#include "core/libraries/ajm/ajm_context.h"
|
||||||
|
#include "core/libraries/ajm/ajm_error.h"
|
||||||
|
#include "core/libraries/ajm/ajm_instance.h"
|
||||||
|
#include "core/libraries/ajm/ajm_mp3.h"
|
||||||
|
#include "core/libraries/error_codes.h"
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
static constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1;
|
||||||
|
|
||||||
|
AjmContext::AjmContext() {
|
||||||
|
worker_thread = std::jthread([this](std::stop_token stop) { this->WorkerThread(stop); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AjmContext::IsRegistered(AjmCodecType type) const {
|
||||||
|
return registered_codecs[std::to_underlying(type)];
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 AjmContext::BatchCancel(const u32 batch_id) {
|
||||||
|
std::shared_ptr<AjmBatch> batch{};
|
||||||
|
{
|
||||||
|
std::shared_lock guard(batches_mutex);
|
||||||
|
const auto p_batch = batches.Get(batch_id);
|
||||||
|
if (p_batch == nullptr) {
|
||||||
|
return ORBIS_AJM_ERROR_INVALID_BATCH;
|
||||||
|
}
|
||||||
|
batch = *p_batch;
|
||||||
|
}
|
||||||
|
|
||||||
|
batch->canceled = true;
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 AjmContext::ModuleRegister(AjmCodecType type) {
|
||||||
|
if (std::to_underlying(type) >= NumAjmCodecs) {
|
||||||
|
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
if (IsRegistered(type)) {
|
||||||
|
return ORBIS_AJM_ERROR_CODEC_ALREADY_REGISTERED;
|
||||||
|
}
|
||||||
|
registered_codecs[std::to_underlying(type)] = true;
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AjmContext::WorkerThread(std::stop_token stop) {
|
||||||
|
while (!stop.stop_requested()) {
|
||||||
|
auto batch = batch_queue.PopWait(stop);
|
||||||
|
if (batch != nullptr) {
|
||||||
|
ProcessBatch(batch->id, batch->jobs);
|
||||||
|
batch->finished.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AjmContext::ProcessBatch(u32 id, std::span<AjmJob> jobs) {
|
||||||
|
// Perform operation requested by control flags.
|
||||||
|
for (auto& job : jobs) {
|
||||||
|
LOG_DEBUG(Lib_Ajm, "Processing job {} for instance {}. flags = {:#x}", id, job.instance_id,
|
||||||
|
job.flags.raw);
|
||||||
|
|
||||||
|
std::shared_ptr<AjmInstance> instance;
|
||||||
|
{
|
||||||
|
std::shared_lock lock(instances_mutex);
|
||||||
|
auto* p_instance = instances.Get(job.instance_id);
|
||||||
|
ASSERT_MSG(p_instance != nullptr, "Attempting to execute job on null instance");
|
||||||
|
instance = *p_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto control_flags = job.flags.control_flags;
|
||||||
|
if (True(control_flags & AjmJobControlFlags::Reset)) {
|
||||||
|
LOG_INFO(Lib_Ajm, "Resetting instance {}", job.instance_id);
|
||||||
|
instance->Reset();
|
||||||
|
}
|
||||||
|
if (True(control_flags & AjmJobControlFlags::Initialize)) {
|
||||||
|
LOG_INFO(Lib_Ajm, "Initializing instance {}", job.instance_id);
|
||||||
|
ASSERT_MSG(job.input.init_params.has_value(),
|
||||||
|
"Initialize called without control buffer");
|
||||||
|
auto& params = job.input.init_params.value();
|
||||||
|
instance->Initialize(¶ms, sizeof(params));
|
||||||
|
}
|
||||||
|
if (True(control_flags & AjmJobControlFlags::Resample)) {
|
||||||
|
LOG_ERROR(Lib_Ajm, "Unimplemented: resample params");
|
||||||
|
ASSERT_MSG(job.input.resample_parameters.has_value(), "Resample paramters are absent");
|
||||||
|
instance->resample_parameters = job.input.resample_parameters.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto sideband_flags = job.flags.sideband_flags;
|
||||||
|
if (True(sideband_flags & AjmJobSidebandFlags::Format)) {
|
||||||
|
ASSERT_MSG(job.input.format.has_value(), "Format parameters are absent");
|
||||||
|
instance->format = job.input.format.value();
|
||||||
|
}
|
||||||
|
if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode)) {
|
||||||
|
ASSERT_MSG(job.input.gapless_decode.has_value(),
|
||||||
|
"Gapless decode parameters are absent");
|
||||||
|
auto& params = job.input.gapless_decode.value();
|
||||||
|
instance->gapless.total_samples = params.total_samples;
|
||||||
|
instance->gapless.skip_samples = params.skip_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!job.input.buffer.empty()) {
|
||||||
|
instance->Decode(&job.input, &job.output);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.output.p_gapless_decode != nullptr) {
|
||||||
|
*job.output.p_gapless_decode = instance->gapless;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.output.p_codec_info != nullptr) {
|
||||||
|
instance->GetCodecInfo(job.output.p_codec_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 AjmContext::BatchWait(const u32 batch_id, const u32 timeout, AjmBatchError* const batch_error) {
|
||||||
|
std::shared_ptr<AjmBatch> batch{};
|
||||||
|
{
|
||||||
|
std::shared_lock guard(batches_mutex);
|
||||||
|
const auto p_batch = batches.Get(batch_id);
|
||||||
|
if (p_batch == nullptr) {
|
||||||
|
return ORBIS_AJM_ERROR_INVALID_BATCH;
|
||||||
|
}
|
||||||
|
batch = *p_batch;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool expected = false;
|
||||||
|
if (!batch->waiting.compare_exchange_strong(expected, true)) {
|
||||||
|
return ORBIS_AJM_ERROR_BUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout == ORBIS_AJM_WAIT_INFINITE) {
|
||||||
|
batch->finished.acquire();
|
||||||
|
} else if (!batch->finished.try_acquire_for(std::chrono::milliseconds(timeout))) {
|
||||||
|
batch->waiting = false;
|
||||||
|
return ORBIS_AJM_ERROR_IN_PROGRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock guard(batches_mutex);
|
||||||
|
batches.Destroy(batch_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batch->canceled) {
|
||||||
|
return ORBIS_AJM_ERROR_CANCELLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AjmContext::BatchStartBuffer(u8* p_batch, u32 batch_size, const int priority,
|
||||||
|
AjmBatchError* batch_error, u32* out_batch_id) {
|
||||||
|
if ((batch_size & 7) != 0) {
|
||||||
|
LOG_ERROR(Lib_Ajm, "ORBIS_AJM_ERROR_MALFORMED_BATCH");
|
||||||
|
return ORBIS_AJM_ERROR_MALFORMED_BATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto batch_info = AjmBatch::FromBatchBuffer({p_batch, batch_size});
|
||||||
|
std::optional<u32> batch_id;
|
||||||
|
{
|
||||||
|
std::unique_lock guard(batches_mutex);
|
||||||
|
batch_id = batches.Create(batch_info);
|
||||||
|
}
|
||||||
|
if (!batch_id.has_value()) {
|
||||||
|
return ORBIS_AJM_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
*out_batch_id = batch_id.value();
|
||||||
|
batch_info->id = *out_batch_id;
|
||||||
|
|
||||||
|
batch_queue.EmplaceWait(batch_info);
|
||||||
|
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags, u32* out_instance) {
|
||||||
|
if (codec_type >= AjmCodecType::Max) {
|
||||||
|
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
if (flags.version == 0) {
|
||||||
|
return ORBIS_AJM_ERROR_WRONG_REVISION_FLAG;
|
||||||
|
}
|
||||||
|
if (!IsRegistered(codec_type)) {
|
||||||
|
return ORBIS_AJM_ERROR_CODEC_NOT_REGISTERED;
|
||||||
|
}
|
||||||
|
ASSERT_MSG(flags.format == 0, "Only signed 16-bit PCM output is supported currently!");
|
||||||
|
std::unique_ptr<AjmInstance> instance;
|
||||||
|
switch (codec_type) {
|
||||||
|
case AjmCodecType::Mp3Dec:
|
||||||
|
instance = std::make_unique<AjmMp3Decoder>();
|
||||||
|
break;
|
||||||
|
case AjmCodecType::At9Dec:
|
||||||
|
instance = std::make_unique<AjmAt9Decoder>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Codec #{} not implemented", u32(codec_type));
|
||||||
|
}
|
||||||
|
instance->codec_type = codec_type;
|
||||||
|
instance->num_channels = flags.channels;
|
||||||
|
instance->flags = flags;
|
||||||
|
|
||||||
|
std::optional<u32> opt_index;
|
||||||
|
{
|
||||||
|
std::unique_lock lock(instances_mutex);
|
||||||
|
opt_index = instances.Create(std::move(instance));
|
||||||
|
}
|
||||||
|
if (!opt_index.has_value()) {
|
||||||
|
return ORBIS_AJM_ERROR_OUT_OF_RESOURCES;
|
||||||
|
}
|
||||||
|
*out_instance = opt_index.value();
|
||||||
|
|
||||||
|
LOG_INFO(Lib_Ajm, "instance = {}", *out_instance);
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 AjmContext::InstanceDestroy(u32 instance) {
|
||||||
|
std::unique_lock lock(instances_mutex);
|
||||||
|
if (!instances.Destroy(instance)) {
|
||||||
|
return ORBIS_AJM_ERROR_INVALID_INSTANCE;
|
||||||
|
}
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Libraries::Ajm
|
56
src/core/libraries/ajm/ajm_context.h
Normal file
56
src/core/libraries/ajm/ajm_context.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/bounded_threadsafe_queue.h"
|
||||||
|
#include "common/slot_array.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "core/libraries/ajm/ajm.h"
|
||||||
|
#include "core/libraries/ajm/ajm_batch.h"
|
||||||
|
#include "core/libraries/ajm/ajm_instance.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <span>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
class AjmContext {
|
||||||
|
public:
|
||||||
|
AjmContext();
|
||||||
|
|
||||||
|
s32 InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags, u32* out_instance_id);
|
||||||
|
s32 InstanceDestroy(u32 instance_id);
|
||||||
|
|
||||||
|
s32 BatchCancel(const u32 batch_id);
|
||||||
|
s32 ModuleRegister(AjmCodecType type);
|
||||||
|
s32 BatchWait(const u32 batch_id, const u32 timeout, AjmBatchError* const p_batch_error);
|
||||||
|
s32 BatchStartBuffer(u8* p_batch, u32 batch_size, const int priority,
|
||||||
|
AjmBatchError* p_batch_error, u32* p_batch_id);
|
||||||
|
|
||||||
|
void WorkerThread(std::stop_token stop);
|
||||||
|
void ProcessBatch(u32 id, std::span<AjmJob> jobs);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr u32 MaxInstances = 0x2fff;
|
||||||
|
static constexpr u32 MaxBatches = 0x0400;
|
||||||
|
static constexpr u32 NumAjmCodecs = u32(AjmCodecType::Max);
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsRegistered(AjmCodecType type) const;
|
||||||
|
|
||||||
|
std::array<bool, NumAjmCodecs> registered_codecs{};
|
||||||
|
|
||||||
|
std::shared_mutex instances_mutex;
|
||||||
|
Common::SlotArray<u32, std::shared_ptr<AjmInstance>, MaxInstances, 1> instances;
|
||||||
|
|
||||||
|
std::shared_mutex batches_mutex;
|
||||||
|
Common::SlotArray<u32, std::shared_ptr<AjmBatch>, MaxBatches, 1> batches;
|
||||||
|
|
||||||
|
std::jthread worker_thread{};
|
||||||
|
Common::MPSCQueue<std::shared_ptr<AjmBatch>> batch_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Libraries::Ajm
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#include "common/enum.h"
|
#include "common/enum.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
#include "core/libraries/ajm/ajm.h"
|
||||||
|
#include "core/libraries/ajm/ajm_batch.h"
|
||||||
|
|
||||||
#include <boost/container/small_vector.hpp>
|
#include <boost/container/small_vector.hpp>
|
||||||
|
|
||||||
@ -33,8 +35,6 @@ constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
|
|||||||
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
|
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
|
||||||
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
||||||
|
|
||||||
constexpr u32 ORBIS_AT9_CONFIG_DATA_SIZE = 4;
|
|
||||||
|
|
||||||
enum class AjmCodecType : u32 {
|
enum class AjmCodecType : u32 {
|
||||||
Mp3Dec = 0,
|
Mp3Dec = 0,
|
||||||
At9Dec = 1,
|
At9Dec = 1,
|
||||||
@ -42,90 +42,12 @@ enum class AjmCodecType : u32 {
|
|||||||
Max = 23,
|
Max = 23,
|
||||||
};
|
};
|
||||||
DECLARE_ENUM_FLAG_OPERATORS(AjmCodecType);
|
DECLARE_ENUM_FLAG_OPERATORS(AjmCodecType);
|
||||||
static constexpr u32 NumAjmCodecs = u32(AjmCodecType::Max);
|
|
||||||
|
|
||||||
enum class AjmFormatEncoding : u32 {
|
|
||||||
S16 = 0,
|
|
||||||
S32 = 1,
|
|
||||||
Float = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmSidebandResult {
|
|
||||||
s32 result;
|
|
||||||
s32 internal_result;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmSidebandMFrame {
|
|
||||||
u32 num_frames;
|
|
||||||
u32 reserved;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmSidebandStream {
|
|
||||||
s32 input_consumed;
|
|
||||||
s32 output_written;
|
|
||||||
u64 total_decoded_samples;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmSidebandFormat {
|
|
||||||
u32 num_channels;
|
|
||||||
u32 channel_mask;
|
|
||||||
u32 sampl_freq;
|
|
||||||
AjmFormatEncoding sample_encoding;
|
|
||||||
u32 bitrate;
|
|
||||||
u32 reserved;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmSidebandGaplessDecode {
|
|
||||||
u32 total_samples;
|
|
||||||
u16 skip_samples;
|
|
||||||
u16 skipped_samples;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmSidebandResampleParameters {
|
|
||||||
float ratio;
|
|
||||||
uint32_t flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmDecAt9InitializeParameters {
|
|
||||||
u8 config_data[ORBIS_AT9_CONFIG_DATA_SIZE];
|
|
||||||
u32 reserved;
|
|
||||||
};
|
|
||||||
|
|
||||||
union AjmSidebandInitParameters {
|
union AjmSidebandInitParameters {
|
||||||
AjmDecAt9InitializeParameters at9;
|
AjmDecAt9InitializeParameters at9;
|
||||||
u8 reserved[8];
|
u8 reserved[8];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AjmJobInput {
|
|
||||||
std::optional<AjmDecAt9InitializeParameters> init_params;
|
|
||||||
std::optional<AjmSidebandResampleParameters> resample_parameters;
|
|
||||||
std::optional<AjmSidebandFormat> format;
|
|
||||||
std::optional<AjmSidebandGaplessDecode> gapless_decode;
|
|
||||||
std::vector<u8> buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmJobOutput {
|
|
||||||
boost::container::small_vector<std::span<u8>, 4> buffers;
|
|
||||||
AjmSidebandResult* p_result = nullptr;
|
|
||||||
AjmSidebandStream* p_stream = nullptr;
|
|
||||||
AjmSidebandFormat* p_format = nullptr;
|
|
||||||
AjmSidebandGaplessDecode* p_gapless_decode = nullptr;
|
|
||||||
AjmSidebandMFrame* p_mframe = nullptr;
|
|
||||||
u8* p_codec_info = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
union AjmInstanceFlags {
|
|
||||||
u64 raw;
|
|
||||||
struct {
|
|
||||||
u64 version : 3;
|
|
||||||
u64 channels : 4;
|
|
||||||
u64 format : 3;
|
|
||||||
u64 gapless_loop : 1;
|
|
||||||
u64 pad : 21;
|
|
||||||
u64 codec : 28;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AjmInstance {
|
struct AjmInstance {
|
||||||
AjmCodecType codec_type;
|
AjmCodecType codec_type;
|
||||||
AjmFormatEncoding fmt{};
|
AjmFormatEncoding fmt{};
|
||||||
@ -148,7 +70,7 @@ struct AjmInstance {
|
|||||||
virtual void GetCodecInfo(void* out_info) = 0;
|
virtual void GetCodecInfo(void* out_info) = 0;
|
||||||
virtual u32 GetCodecInfoSize() = 0;
|
virtual u32 GetCodecInfoSize() = 0;
|
||||||
|
|
||||||
virtual void Decode(const AjmJobInput* input, AjmJobOutput* output) = 0;
|
virtual void Decode(const AjmJob::Input* input, AjmJob::Output* output) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Libraries::Ajm
|
} // namespace Libraries::Ajm
|
||||||
|
@ -77,7 +77,7 @@ void AjmMp3Decoder::Reset() {
|
|||||||
gapless_decoded_samples = 0;
|
gapless_decoded_samples = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AjmMp3Decoder::Decode(const AjmJobInput* input, AjmJobOutput* output) {
|
void AjmMp3Decoder::Decode(const AjmJob::Input* input, AjmJob::Output* output) {
|
||||||
AVPacket* pkt = av_packet_alloc();
|
AVPacket* pkt = av_packet_alloc();
|
||||||
|
|
||||||
size_t out_buffer_index = 0;
|
size_t out_buffer_index = 0;
|
||||||
|
@ -74,7 +74,7 @@ struct AjmMp3Decoder : public AjmInstance {
|
|||||||
return sizeof(AjmSidebandDecMp3CodecInfo);
|
return sizeof(AjmSidebandDecMp3CodecInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Decode(const AjmJobInput* input, AjmJobOutput* output) override;
|
void Decode(const AjmJob::Input* input, AjmJob::Output* output) override;
|
||||||
|
|
||||||
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
||||||
AjmDecMp3ParseFrame* frame);
|
AjmDecMp3ParseFrame* frame);
|
||||||
|
Loading…
Reference in New Issue
Block a user