Skip to content

Commit

Permalink
AJM: Added some missing features (shadps4-emu#1489)
Browse files Browse the repository at this point in the history
* AJM: Added support for different PCM formats

* updated libatrac9 ref

* remove log

* Add support for non-interleaved flag

* Added support for output sideband format query
  • Loading branch information
vladmikhalin authored and fpiesche committed Nov 7, 2024
1 parent b2f2340 commit a8a85ef
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 82 deletions.
2 changes: 1 addition & 1 deletion externals/LibAtrac9
23 changes: 23 additions & 0 deletions src/core/libraries/ajm/ajm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,31 @@

namespace Libraries::Ajm {

constexpr int ORBIS_AJM_CHANNELMASK_MONO = 0x0004;
constexpr int ORBIS_AJM_CHANNELMASK_STEREO = 0x0003;
constexpr int ORBIS_AJM_CHANNELMASK_QUAD = 0x0033;
constexpr int ORBIS_AJM_CHANNELMASK_5POINT1 = 0x060F;
constexpr int ORBIS_AJM_CHANNELMASK_7POINT1 = 0x063F;

static std::unique_ptr<AjmContext> context{};

u32 GetChannelMask(u32 num_channels) {
switch (num_channels) {
case 1:
return ORBIS_AJM_CHANNELMASK_MONO;
case 2:
return ORBIS_AJM_CHANNELMASK_STEREO;
case 4:
return ORBIS_AJM_CHANNELMASK_QUAD;
case 6:
return ORBIS_AJM_CHANNELMASK_5POINT1;
case 8:
return ORBIS_AJM_CHANNELMASK_7POINT1;
default:
UNREACHABLE();
}
}

int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id) {
LOG_INFO(Lib_Ajm, "called context_id = {} batch_id = {}", context_id, batch_id);
return context->BatchCancel(batch_id);
Expand Down
3 changes: 3 additions & 0 deletions src/core/libraries/ajm/ajm.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,12 @@ union AjmInstanceFlags {
u64 codec : 28;
};
};
static_assert(sizeof(AjmInstanceFlags) == 8);

struct AjmDecMp3ParseFrame;

u32 GetChannelMask(u32 num_channels);

int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id);
int PS4_SYSV_ABI sceAjmBatchErrorDump();
void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(void* p_buffer, u32 instance_id, u64 flags,
Expand Down
84 changes: 68 additions & 16 deletions src/core/libraries/ajm/ajm_at9.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ extern "C" {

namespace Libraries::Ajm {

AjmAt9Decoder::AjmAt9Decoder() {
m_handle = Atrac9GetHandle();
AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags)
: m_format(format), m_flags(flags), m_handle(Atrac9GetHandle()) {
ASSERT_MSG(m_handle, "Atrac9GetHandle failed");
AjmAt9Decoder::Reset();
}
Expand All @@ -40,7 +40,20 @@ void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) {
const auto params = reinterpret_cast<const AjmDecAt9InitializeParameters*>(buffer);
std::memcpy(m_config_data, params->config_data, ORBIS_AT9_CONFIG_DATA_SIZE);
AjmAt9Decoder::Reset();
m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels, 0);
m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels * GetPointCodeSize(), 0);
}

u8 AjmAt9Decoder::GetPointCodeSize() {
switch (m_format) {
case AjmFormatEncoding::S16:
return sizeof(s16);
case AjmFormatEncoding::S32:
return sizeof(s32);
case AjmFormatEncoding::Float:
return sizeof(float);
default:
UNREACHABLE();
}
}

void AjmAt9Decoder::GetInfo(void* out_info) {
Expand All @@ -53,28 +66,56 @@ void AjmAt9Decoder::GetInfo(void* out_info) {

std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless,
u32 max_samples_per_channel) {
std::optional<u32> max_samples_per_channel) {
int ret = 0;
int bytes_used = 0;
u32 ret = Atrac9Decode(m_handle, in_buf.data(), m_pcm_buffer.data(), &bytes_used);
switch (m_format) {
case AjmFormatEncoding::S16:
ret = Atrac9Decode(m_handle, in_buf.data(), reinterpret_cast<s16*>(m_pcm_buffer.data()),
&bytes_used, True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
break;
case AjmFormatEncoding::S32:
ret = Atrac9DecodeS32(m_handle, in_buf.data(), reinterpret_cast<s32*>(m_pcm_buffer.data()),
&bytes_used, True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
break;
case AjmFormatEncoding::Float:
ret =
Atrac9DecodeF32(m_handle, in_buf.data(), reinterpret_cast<float*>(m_pcm_buffer.data()),
&bytes_used, True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
break;
default:
UNREACHABLE();
}
ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
in_buf = in_buf.subspan(bytes_used);

m_superframe_bytes_remain -= bytes_used;
std::span<s16> pcm_data{m_pcm_buffer};

u32 skipped_samples = 0;
if (gapless.skipped_samples < gapless.skip_samples) {
const auto skipped_samples = std::min(u32(m_codec_info.frameSamples),
u32(gapless.skip_samples - gapless.skipped_samples));
skipped_samples = std::min(u32(m_codec_info.frameSamples),
u32(gapless.skip_samples - gapless.skipped_samples));
gapless.skipped_samples += skipped_samples;
pcm_data = pcm_data.subspan(skipped_samples * m_codec_info.channels);
}

const auto max_samples = max_samples_per_channel == std::numeric_limits<u32>::max()
? max_samples_per_channel
: max_samples_per_channel * m_codec_info.channels;

const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
const auto written = output.Write(pcm_data.subspan(0, pcm_size));
const auto max_samples = max_samples_per_channel.has_value()
? max_samples_per_channel.value() * m_codec_info.channels
: std::numeric_limits<u32>::max();

size_t samples_written = 0;
switch (m_format) {
case AjmFormatEncoding::S16:
samples_written = WriteOutputSamples<s16>(output, skipped_samples, max_samples);
break;
case AjmFormatEncoding::S32:
samples_written = WriteOutputSamples<s32>(output, skipped_samples, max_samples);
break;
case AjmFormatEncoding::Float:
samples_written = WriteOutputSamples<float>(output, skipped_samples, max_samples);
break;
default:
UNREACHABLE();
}

m_num_frames += 1;
if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) {
Expand All @@ -85,7 +126,18 @@ std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
m_num_frames = 0;
}

return {1, (written / m_codec_info.channels) / sizeof(s16)};
return {1, samples_written / m_codec_info.channels};
}

AjmSidebandFormat AjmAt9Decoder::GetFormat() {
return AjmSidebandFormat{
.num_channels = u32(m_codec_info.channels),
.channel_mask = GetChannelMask(u32(m_codec_info.channels)),
.sampl_freq = u32(m_codec_info.samplingRate),
.sample_encoding = m_format,
.bitrate = u32(m_codec_info.samplingRate * GetPointCodeSize() * 8),
.reserved = 0,
};
}

} // namespace Libraries::Ajm
29 changes: 26 additions & 3 deletions src/core/libraries/ajm/ajm_at9.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@

#include "libatrac9.h"

#include <span>

namespace Libraries::Ajm {

constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8;

enum AjmAt9CodecFlags : u32 {
ParseRiffHeader = 1 << 0,
NonInterleavedOutput = 1 << 8,
};
DECLARE_ENUM_FLAG_OPERATORS(AjmAt9CodecFlags)

struct AjmSidebandDecAt9CodecInfo {
u32 super_frame_size;
u32 frames_in_super_frame;
Expand All @@ -20,22 +28,37 @@ struct AjmSidebandDecAt9CodecInfo {
};

struct AjmAt9Decoder final : AjmCodec {
explicit AjmAt9Decoder();
explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags);
~AjmAt9Decoder() override;

void Reset() override;
void Initialize(const void* buffer, u32 buffer_size) override;
void GetInfo(void* out_info) override;
AjmSidebandFormat GetFormat() override;
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless, u32 max_samples) override;
AjmSidebandGaplessDecode& gapless,
std::optional<u32> max_samples) override;

private:
u8 GetPointCodeSize();

template <class T>
size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_samples, u32 max_samples) {
std::span<T> pcm_data{reinterpret_cast<T*>(m_pcm_buffer.data()),
m_pcm_buffer.size() / sizeof(T)};
pcm_data = pcm_data.subspan(skipped_samples * m_codec_info.channels);
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
return output.Write(pcm_data.subspan(0, pcm_size));
}

const AjmFormatEncoding m_format;
const AjmAt9CodecFlags m_flags;
void* m_handle{};
u8 m_config_data[ORBIS_AT9_CONFIG_DATA_SIZE]{};
u32 m_superframe_bytes_remain{};
u32 m_num_frames{};
Atrac9CodecInfo m_codec_info{};
std::vector<s16> m_pcm_buffer;
std::vector<u8> m_pcm_buffer;
};

} // namespace Libraries::Ajm
1 change: 0 additions & 1 deletion src/core/libraries/ajm/ajm_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags,
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::optional<u32> opt_index;
{
std::unique_lock lock(instances_mutex);
Expand Down
28 changes: 23 additions & 5 deletions src/core/libraries/ajm/ajm_instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,28 @@

namespace Libraries::Ajm {

constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;

AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
switch (codec_type) {
case AjmCodecType::At9Dec: {
m_codec = std::make_unique<AjmAt9Decoder>();
m_codec = std::make_unique<AjmAt9Decoder>(AjmFormatEncoding(flags.format),
AjmAt9CodecFlags(flags.codec));
break;
}
case AjmCodecType::Mp3Dec: {
m_codec = std::make_unique<AjmMp3Decoder>();
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format));
break;
}
default:
Expand Down Expand Up @@ -62,9 +76,10 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
auto in_size = in_buf.size();
auto out_size = out_buf.Size();
while (!in_buf.empty() && !out_buf.IsEmpty() && !IsGaplessEnd()) {
const u32 samples_remain = m_gapless.total_samples != 0
? m_gapless.total_samples - m_gapless_samples
: std::numeric_limits<u32>::max();
const auto samples_remain =
m_gapless.total_samples != 0
? std::optional<u32>{m_gapless.total_samples - m_gapless_samples}
: std::optional<u32>{};
const auto [nframes, nsamples] =
m_codec->ProcessData(in_buf, out_buf, m_gapless, samples_remain);
frames_decoded += nframes;
Expand All @@ -87,6 +102,9 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
m_gapless.skipped_samples = 0;
m_codec->Reset();
}
if (job.output.p_format != nullptr) {
*job.output.p_format = m_codec->GetFormat();
}
if (job.output.p_gapless_decode != nullptr) {
*job.output.p_gapless_decode = m_gapless;
}
Expand Down
33 changes: 7 additions & 26 deletions src/core/libraries/ajm/ajm_instance.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,26 @@

namespace Libraries::Ajm {

constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;

class SparseOutputBuffer {
public:
SparseOutputBuffer(std::span<std::span<u8>> chunks)
: m_chunks(chunks), m_current(m_chunks.begin()) {}

template <class T>
size_t Write(std::span<T> pcm) {
size_t bytes_written = 0;
size_t samples_written = 0;
while (!pcm.empty() && !IsEmpty()) {
auto size = std::min(pcm.size() * sizeof(T), m_current->size());
std::memcpy(m_current->data(), pcm.data(), size);
bytes_written += size;
pcm = pcm.subspan(size / sizeof(T));
const auto nsamples = size / sizeof(T);
samples_written += nsamples;
pcm = pcm.subspan(nsamples);
*m_current = m_current->subspan(size);
if (m_current->empty()) {
++m_current;
}
}
return bytes_written;
return samples_written;
}

bool IsEmpty() {
Expand All @@ -65,21 +53,17 @@ class SparseOutputBuffer {
std::span<std::span<u8>>::iterator m_current;
};

struct DecodeResult {
u32 bytes_consumed{};
u32 bytes_written{};
};

class AjmCodec {
public:
virtual ~AjmCodec() = default;

virtual void Initialize(const void* buffer, u32 buffer_size) = 0;
virtual void Reset() = 0;
virtual void GetInfo(void* out_info) = 0;
virtual AjmSidebandFormat GetFormat() = 0;
virtual std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless,
u32 max_samples) = 0;
std::optional<u32> max_samples_per_channel) = 0;
};

class AjmInstance {
Expand All @@ -100,9 +84,6 @@ class AjmInstance {
u32 m_total_samples{};

std::unique_ptr<AjmCodec> m_codec;

// AjmCodecType codec_type;
// u32 index{};
};

} // namespace Libraries::Ajm
Loading

0 comments on commit a8a85ef

Please sign in to comment.