mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2024-11-23 03:09:55 +00:00
AJM: Added some missing features (#1489)
* 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
This commit is contained in:
parent
f98b9f7726
commit
46ac48c311
2
externals/LibAtrac9
vendored
2
externals/LibAtrac9
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 82767fe38823c32536726ea798f392b0b49e66b9
|
Subproject commit 3acdcdc78f129c2e6145331ff650fa76dd88d62c
|
@ -13,8 +13,31 @@
|
|||||||
|
|
||||||
namespace Libraries::Ajm {
|
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{};
|
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) {
|
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);
|
LOG_INFO(Lib_Ajm, "called context_id = {} batch_id = {}", context_id, batch_id);
|
||||||
return context->BatchCancel(batch_id);
|
return context->BatchCancel(batch_id);
|
||||||
|
@ -137,9 +137,12 @@ union AjmInstanceFlags {
|
|||||||
u64 codec : 28;
|
u64 codec : 28;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
static_assert(sizeof(AjmInstanceFlags) == 8);
|
||||||
|
|
||||||
struct AjmDecMp3ParseFrame;
|
struct AjmDecMp3ParseFrame;
|
||||||
|
|
||||||
|
u32 GetChannelMask(u32 num_channels);
|
||||||
|
|
||||||
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);
|
||||||
int PS4_SYSV_ABI sceAjmBatchErrorDump();
|
int PS4_SYSV_ABI sceAjmBatchErrorDump();
|
||||||
void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(void* p_buffer, u32 instance_id, u64 flags,
|
void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(void* p_buffer, u32 instance_id, u64 flags,
|
||||||
|
@ -14,8 +14,8 @@ extern "C" {
|
|||||||
|
|
||||||
namespace Libraries::Ajm {
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
AjmAt9Decoder::AjmAt9Decoder() {
|
AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags)
|
||||||
m_handle = Atrac9GetHandle();
|
: m_format(format), m_flags(flags), m_handle(Atrac9GetHandle()) {
|
||||||
ASSERT_MSG(m_handle, "Atrac9GetHandle failed");
|
ASSERT_MSG(m_handle, "Atrac9GetHandle failed");
|
||||||
AjmAt9Decoder::Reset();
|
AjmAt9Decoder::Reset();
|
||||||
}
|
}
|
||||||
@ -40,7 +40,20 @@ void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) {
|
|||||||
const auto params = reinterpret_cast<const AjmDecAt9InitializeParameters*>(buffer);
|
const auto params = reinterpret_cast<const AjmDecAt9InitializeParameters*>(buffer);
|
||||||
std::memcpy(m_config_data, params->config_data, ORBIS_AT9_CONFIG_DATA_SIZE);
|
std::memcpy(m_config_data, params->config_data, ORBIS_AT9_CONFIG_DATA_SIZE);
|
||||||
AjmAt9Decoder::Reset();
|
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) {
|
void AjmAt9Decoder::GetInfo(void* out_info) {
|
||||||
@ -53,28 +66,56 @@ void AjmAt9Decoder::GetInfo(void* out_info) {
|
|||||||
|
|
||||||
std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||||
AjmSidebandGaplessDecode& gapless,
|
AjmSidebandGaplessDecode& gapless,
|
||||||
u32 max_samples_per_channel) {
|
std::optional<u32> max_samples_per_channel) {
|
||||||
|
int ret = 0;
|
||||||
int bytes_used = 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);
|
ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
|
||||||
in_buf = in_buf.subspan(bytes_used);
|
in_buf = in_buf.subspan(bytes_used);
|
||||||
|
|
||||||
m_superframe_bytes_remain -= 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) {
|
if (gapless.skipped_samples < gapless.skip_samples) {
|
||||||
const auto skipped_samples = std::min(u32(m_codec_info.frameSamples),
|
skipped_samples = std::min(u32(m_codec_info.frameSamples),
|
||||||
u32(gapless.skip_samples - gapless.skipped_samples));
|
u32(gapless.skip_samples - gapless.skipped_samples));
|
||||||
gapless.skipped_samples += 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()
|
const auto max_samples = max_samples_per_channel.has_value()
|
||||||
? max_samples_per_channel
|
? max_samples_per_channel.value() * m_codec_info.channels
|
||||||
: max_samples_per_channel * m_codec_info.channels;
|
: std::numeric_limits<u32>::max();
|
||||||
|
|
||||||
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
|
size_t samples_written = 0;
|
||||||
const auto written = output.Write(pcm_data.subspan(0, pcm_size));
|
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;
|
m_num_frames += 1;
|
||||||
if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) {
|
if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) {
|
||||||
@ -85,7 +126,18 @@ std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
|
|||||||
m_num_frames = 0;
|
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
|
} // namespace Libraries::Ajm
|
||||||
|
@ -8,10 +8,18 @@
|
|||||||
|
|
||||||
#include "libatrac9.h"
|
#include "libatrac9.h"
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
|
||||||
namespace Libraries::Ajm {
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8;
|
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 {
|
struct AjmSidebandDecAt9CodecInfo {
|
||||||
u32 super_frame_size;
|
u32 super_frame_size;
|
||||||
u32 frames_in_super_frame;
|
u32 frames_in_super_frame;
|
||||||
@ -20,22 +28,37 @@ struct AjmSidebandDecAt9CodecInfo {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct AjmAt9Decoder final : AjmCodec {
|
struct AjmAt9Decoder final : AjmCodec {
|
||||||
explicit AjmAt9Decoder();
|
explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags);
|
||||||
~AjmAt9Decoder() override;
|
~AjmAt9Decoder() override;
|
||||||
|
|
||||||
void Reset() override;
|
void Reset() override;
|
||||||
void Initialize(const void* buffer, u32 buffer_size) override;
|
void Initialize(const void* buffer, u32 buffer_size) override;
|
||||||
void GetInfo(void* out_info) override;
|
void GetInfo(void* out_info) override;
|
||||||
|
AjmSidebandFormat GetFormat() override;
|
||||||
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
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:
|
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{};
|
void* m_handle{};
|
||||||
u8 m_config_data[ORBIS_AT9_CONFIG_DATA_SIZE]{};
|
u8 m_config_data[ORBIS_AT9_CONFIG_DATA_SIZE]{};
|
||||||
u32 m_superframe_bytes_remain{};
|
u32 m_superframe_bytes_remain{};
|
||||||
u32 m_num_frames{};
|
u32 m_num_frames{};
|
||||||
Atrac9CodecInfo m_codec_info{};
|
Atrac9CodecInfo m_codec_info{};
|
||||||
std::vector<s16> m_pcm_buffer;
|
std::vector<u8> m_pcm_buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Libraries::Ajm
|
} // namespace Libraries::Ajm
|
||||||
|
@ -149,7 +149,6 @@ s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags,
|
|||||||
if (!IsRegistered(codec_type)) {
|
if (!IsRegistered(codec_type)) {
|
||||||
return ORBIS_AJM_ERROR_CODEC_NOT_REGISTERED;
|
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::optional<u32> opt_index;
|
||||||
{
|
{
|
||||||
std::unique_lock lock(instances_mutex);
|
std::unique_lock lock(instances_mutex);
|
||||||
|
@ -9,14 +9,28 @@
|
|||||||
|
|
||||||
namespace Libraries::Ajm {
|
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) {
|
AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
|
||||||
switch (codec_type) {
|
switch (codec_type) {
|
||||||
case AjmCodecType::At9Dec: {
|
case AjmCodecType::At9Dec: {
|
||||||
m_codec = std::make_unique<AjmAt9Decoder>();
|
m_codec = std::make_unique<AjmAt9Decoder>(AjmFormatEncoding(flags.format),
|
||||||
|
AjmAt9CodecFlags(flags.codec));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AjmCodecType::Mp3Dec: {
|
case AjmCodecType::Mp3Dec: {
|
||||||
m_codec = std::make_unique<AjmMp3Decoder>();
|
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -62,9 +76,10 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
|||||||
auto in_size = in_buf.size();
|
auto in_size = in_buf.size();
|
||||||
auto out_size = out_buf.Size();
|
auto out_size = out_buf.Size();
|
||||||
while (!in_buf.empty() && !out_buf.IsEmpty() && !IsGaplessEnd()) {
|
while (!in_buf.empty() && !out_buf.IsEmpty() && !IsGaplessEnd()) {
|
||||||
const u32 samples_remain = m_gapless.total_samples != 0
|
const auto samples_remain =
|
||||||
? m_gapless.total_samples - m_gapless_samples
|
m_gapless.total_samples != 0
|
||||||
: std::numeric_limits<u32>::max();
|
? std::optional<u32>{m_gapless.total_samples - m_gapless_samples}
|
||||||
|
: std::optional<u32>{};
|
||||||
const auto [nframes, nsamples] =
|
const auto [nframes, nsamples] =
|
||||||
m_codec->ProcessData(in_buf, out_buf, m_gapless, samples_remain);
|
m_codec->ProcessData(in_buf, out_buf, m_gapless, samples_remain);
|
||||||
frames_decoded += nframes;
|
frames_decoded += nframes;
|
||||||
@ -87,6 +102,9 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
|||||||
m_gapless.skipped_samples = 0;
|
m_gapless.skipped_samples = 0;
|
||||||
m_codec->Reset();
|
m_codec->Reset();
|
||||||
}
|
}
|
||||||
|
if (job.output.p_format != nullptr) {
|
||||||
|
*job.output.p_format = m_codec->GetFormat();
|
||||||
|
}
|
||||||
if (job.output.p_gapless_decode != nullptr) {
|
if (job.output.p_gapless_decode != nullptr) {
|
||||||
*job.output.p_gapless_decode = m_gapless;
|
*job.output.p_gapless_decode = m_gapless;
|
||||||
}
|
}
|
||||||
|
@ -14,19 +14,6 @@
|
|||||||
|
|
||||||
namespace Libraries::Ajm {
|
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 {
|
class SparseOutputBuffer {
|
||||||
public:
|
public:
|
||||||
SparseOutputBuffer(std::span<std::span<u8>> chunks)
|
SparseOutputBuffer(std::span<std::span<u8>> chunks)
|
||||||
@ -34,18 +21,19 @@ public:
|
|||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
size_t Write(std::span<T> pcm) {
|
size_t Write(std::span<T> pcm) {
|
||||||
size_t bytes_written = 0;
|
size_t samples_written = 0;
|
||||||
while (!pcm.empty() && !IsEmpty()) {
|
while (!pcm.empty() && !IsEmpty()) {
|
||||||
auto size = std::min(pcm.size() * sizeof(T), m_current->size());
|
auto size = std::min(pcm.size() * sizeof(T), m_current->size());
|
||||||
std::memcpy(m_current->data(), pcm.data(), size);
|
std::memcpy(m_current->data(), pcm.data(), size);
|
||||||
bytes_written += size;
|
const auto nsamples = size / sizeof(T);
|
||||||
pcm = pcm.subspan(size / sizeof(T));
|
samples_written += nsamples;
|
||||||
|
pcm = pcm.subspan(nsamples);
|
||||||
*m_current = m_current->subspan(size);
|
*m_current = m_current->subspan(size);
|
||||||
if (m_current->empty()) {
|
if (m_current->empty()) {
|
||||||
++m_current;
|
++m_current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bytes_written;
|
return samples_written;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsEmpty() {
|
bool IsEmpty() {
|
||||||
@ -65,11 +53,6 @@ private:
|
|||||||
std::span<std::span<u8>>::iterator m_current;
|
std::span<std::span<u8>>::iterator m_current;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DecodeResult {
|
|
||||||
u32 bytes_consumed{};
|
|
||||||
u32 bytes_written{};
|
|
||||||
};
|
|
||||||
|
|
||||||
class AjmCodec {
|
class AjmCodec {
|
||||||
public:
|
public:
|
||||||
virtual ~AjmCodec() = default;
|
virtual ~AjmCodec() = default;
|
||||||
@ -77,9 +60,10 @@ public:
|
|||||||
virtual void Initialize(const void* buffer, u32 buffer_size) = 0;
|
virtual void Initialize(const void* buffer, u32 buffer_size) = 0;
|
||||||
virtual void Reset() = 0;
|
virtual void Reset() = 0;
|
||||||
virtual void GetInfo(void* out_info) = 0;
|
virtual void GetInfo(void* out_info) = 0;
|
||||||
|
virtual AjmSidebandFormat GetFormat() = 0;
|
||||||
virtual std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
virtual std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||||
AjmSidebandGaplessDecode& gapless,
|
AjmSidebandGaplessDecode& gapless,
|
||||||
u32 max_samples) = 0;
|
std::optional<u32> max_samples_per_channel) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AjmInstance {
|
class AjmInstance {
|
||||||
@ -100,9 +84,6 @@ private:
|
|||||||
u32 m_total_samples{};
|
u32 m_total_samples{};
|
||||||
|
|
||||||
std::unique_ptr<AjmCodec> m_codec;
|
std::unique_ptr<AjmCodec> m_codec;
|
||||||
|
|
||||||
// AjmCodecType codec_type;
|
|
||||||
// u32 index{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Libraries::Ajm
|
} // namespace Libraries::Ajm
|
||||||
|
@ -30,9 +30,27 @@ static constexpr std::array<s32, 2> UnkTable = {0x48, 0x90};
|
|||||||
|
|
||||||
SwrContext* swr_context{};
|
SwrContext* swr_context{};
|
||||||
|
|
||||||
AVFrame* ConvertAudioFrame(AVFrame* frame) {
|
static AVSampleFormat AjmToAVSampleFormat(AjmFormatEncoding format) {
|
||||||
|
switch (format) {
|
||||||
|
case AjmFormatEncoding::S16:
|
||||||
|
return AV_SAMPLE_FMT_S16;
|
||||||
|
case AjmFormatEncoding::S32:
|
||||||
|
return AV_SAMPLE_FMT_S32;
|
||||||
|
case AjmFormatEncoding::Float:
|
||||||
|
return AV_SAMPLE_FMT_FLT;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) {
|
||||||
|
AVSampleFormat format = AjmToAVSampleFormat(m_format);
|
||||||
|
if (frame->format == format) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
auto pcm16_frame = av_frame_clone(frame);
|
auto pcm16_frame = av_frame_clone(frame);
|
||||||
pcm16_frame->format = AV_SAMPLE_FMT_S16;
|
pcm16_frame->format = format;
|
||||||
|
|
||||||
if (swr_context) {
|
if (swr_context) {
|
||||||
swr_free(&swr_context);
|
swr_free(&swr_context);
|
||||||
@ -40,9 +58,9 @@ AVFrame* ConvertAudioFrame(AVFrame* frame) {
|
|||||||
}
|
}
|
||||||
AVChannelLayout in_ch_layout = frame->ch_layout;
|
AVChannelLayout in_ch_layout = frame->ch_layout;
|
||||||
AVChannelLayout out_ch_layout = pcm16_frame->ch_layout;
|
AVChannelLayout out_ch_layout = pcm16_frame->ch_layout;
|
||||||
swr_alloc_set_opts2(&swr_context, &out_ch_layout, AV_SAMPLE_FMT_S16, frame->sample_rate,
|
swr_alloc_set_opts2(&swr_context, &out_ch_layout, AVSampleFormat(pcm16_frame->format),
|
||||||
&in_ch_layout, AVSampleFormat(frame->format), frame->sample_rate, 0,
|
frame->sample_rate, &in_ch_layout, AVSampleFormat(frame->format),
|
||||||
nullptr);
|
frame->sample_rate, 0, nullptr);
|
||||||
swr_init(swr_context);
|
swr_init(swr_context);
|
||||||
const auto res = swr_convert_frame(swr_context, pcm16_frame, frame);
|
const auto res = swr_convert_frame(swr_context, pcm16_frame, frame);
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
@ -53,11 +71,9 @@ AVFrame* ConvertAudioFrame(AVFrame* frame) {
|
|||||||
return pcm16_frame;
|
return pcm16_frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
AjmMp3Decoder::AjmMp3Decoder() {
|
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format)
|
||||||
m_codec = avcodec_find_decoder(AV_CODEC_ID_MP3);
|
: m_format(format), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)),
|
||||||
ASSERT_MSG(m_codec, "MP3 m_codec not found");
|
m_parser(av_parser_init(m_codec->id)) {
|
||||||
m_parser = av_parser_init(m_codec->id);
|
|
||||||
ASSERT_MSG(m_parser, "Parser not found");
|
|
||||||
AjmMp3Decoder::Reset();
|
AjmMp3Decoder::Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +97,7 @@ void AjmMp3Decoder::GetInfo(void* out_info) {
|
|||||||
|
|
||||||
std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||||
AjmSidebandGaplessDecode& gapless,
|
AjmSidebandGaplessDecode& gapless,
|
||||||
u32 max_samples) {
|
std::optional<u32> max_samples_per_channel) {
|
||||||
AVPacket* pkt = av_packet_alloc();
|
AVPacket* pkt = av_packet_alloc();
|
||||||
|
|
||||||
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
|
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
|
||||||
@ -109,24 +125,37 @@ std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
|
|||||||
} else if (ret < 0) {
|
} else if (ret < 0) {
|
||||||
UNREACHABLE_MSG("Error during decoding");
|
UNREACHABLE_MSG("Error during decoding");
|
||||||
}
|
}
|
||||||
if (frame->format != AV_SAMPLE_FMT_S16) {
|
|
||||||
frame = ConvertAudioFrame(frame);
|
frame = ConvertAudioFrame(frame);
|
||||||
}
|
|
||||||
|
|
||||||
frames_decoded += 1;
|
frames_decoded += 1;
|
||||||
samples_decoded += frame->nb_samples;
|
u32 skipped_samples = 0;
|
||||||
const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(u16);
|
|
||||||
std::span<s16> pcm_data(reinterpret_cast<s16*>(frame->data[0]), size >> 1);
|
|
||||||
if (gapless.skipped_samples < gapless.skip_samples) {
|
if (gapless.skipped_samples < gapless.skip_samples) {
|
||||||
const auto skipped_samples = std::min(
|
skipped_samples = std::min(u32(frame->nb_samples),
|
||||||
u32(frame->nb_samples), u32(gapless.skip_samples - gapless.skipped_samples));
|
u32(gapless.skip_samples - gapless.skipped_samples));
|
||||||
gapless.skipped_samples += skipped_samples;
|
gapless.skipped_samples += skipped_samples;
|
||||||
pcm_data = pcm_data.subspan(skipped_samples * frame->ch_layout.nb_channels);
|
|
||||||
samples_decoded -= skipped_samples;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
|
const auto max_samples =
|
||||||
output.Write(pcm_data.subspan(0, pcm_size));
|
max_samples_per_channel.has_value()
|
||||||
|
? max_samples_per_channel.value() * frame->ch_layout.nb_channels
|
||||||
|
: std::numeric_limits<u32>::max();
|
||||||
|
|
||||||
|
switch (m_format) {
|
||||||
|
case AjmFormatEncoding::S16:
|
||||||
|
samples_decoded +=
|
||||||
|
WriteOutputSamples<s16>(frame, output, skipped_samples, max_samples);
|
||||||
|
break;
|
||||||
|
case AjmFormatEncoding::S32:
|
||||||
|
samples_decoded +=
|
||||||
|
WriteOutputSamples<s32>(frame, output, skipped_samples, max_samples);
|
||||||
|
break;
|
||||||
|
case AjmFormatEncoding::Float:
|
||||||
|
samples_decoded +=
|
||||||
|
WriteOutputSamples<float>(frame, output, skipped_samples, max_samples);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
}
|
}
|
||||||
@ -163,4 +192,9 @@ int AjmMp3Decoder::ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AjmSidebandFormat AjmMp3Decoder::GetFormat() {
|
||||||
|
LOG_ERROR(Lib_Ajm, "Unimplemented");
|
||||||
|
return AjmSidebandFormat{};
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Libraries::Ajm
|
} // namespace Libraries::Ajm
|
||||||
|
@ -7,11 +7,7 @@
|
|||||||
#include "core/libraries/ajm/ajm_instance.h"
|
#include "core/libraries/ajm/ajm_instance.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
struct AVCodec;
|
#include <libavcodec/avcodec.h>
|
||||||
struct AVCodecContext;
|
|
||||||
struct AVCodecParserContext;
|
|
||||||
struct AVFrame;
|
|
||||||
struct AVPacket;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Libraries::Ajm {
|
namespace Libraries::Ajm {
|
||||||
@ -54,19 +50,35 @@ struct AjmSidebandDecMp3CodecInfo {
|
|||||||
|
|
||||||
class AjmMp3Decoder : public AjmCodec {
|
class AjmMp3Decoder : public AjmCodec {
|
||||||
public:
|
public:
|
||||||
explicit AjmMp3Decoder();
|
explicit AjmMp3Decoder(AjmFormatEncoding format);
|
||||||
~AjmMp3Decoder() override;
|
~AjmMp3Decoder() override;
|
||||||
|
|
||||||
void Reset() override;
|
void Reset() override;
|
||||||
void Initialize(const void* buffer, u32 buffer_size) override {}
|
void Initialize(const void* buffer, u32 buffer_size) override {}
|
||||||
void GetInfo(void* out_info) override;
|
void GetInfo(void* out_info) override;
|
||||||
|
AjmSidebandFormat GetFormat() override;
|
||||||
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||||
AjmSidebandGaplessDecode& gapless, u32 max_samples) override;
|
AjmSidebandGaplessDecode& gapless,
|
||||||
|
std::optional<u32> max_samples_per_channel) 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);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
template <class T>
|
||||||
|
size_t WriteOutputSamples(AVFrame* frame, SparseOutputBuffer& output, u32 skipped_samples,
|
||||||
|
u32 max_samples) {
|
||||||
|
const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(T);
|
||||||
|
std::span<T> pcm_data(reinterpret_cast<T*>(frame->data[0]), size >> 1);
|
||||||
|
pcm_data = pcm_data.subspan(skipped_samples * frame->ch_layout.nb_channels);
|
||||||
|
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
|
||||||
|
const auto samples_written = output.Write(pcm_data.subspan(0, pcm_size));
|
||||||
|
return samples_written / frame->ch_layout.nb_channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVFrame* ConvertAudioFrame(AVFrame* frame);
|
||||||
|
|
||||||
|
const AjmFormatEncoding m_format;
|
||||||
const AVCodec* m_codec = nullptr;
|
const AVCodec* m_codec = nullptr;
|
||||||
AVCodecContext* m_codec_context = nullptr;
|
AVCodecContext* m_codec_context = nullptr;
|
||||||
AVCodecParserContext* m_parser = nullptr;
|
AVCodecParserContext* m_parser = nullptr;
|
||||||
|
Loading…
Reference in New Issue
Block a user