Skip to content

Commit bf239eb

Browse files
authored
ajm: handle single-frame decode jobs (+mp3 imrovements) (#1520)
* ajm: handle single-frame decode jobs (+mp3 imrovements) * disable breaking the loop in multi-frame if storage is insufficient * simplified gapless decoding
1 parent 8e28157 commit bf239eb

File tree

7 files changed

+442
-148
lines changed

7 files changed

+442
-148
lines changed

src/core/libraries/ajm/ajm_at9.cpp

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,11 @@ void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) {
4040
const auto params = reinterpret_cast<const AjmDecAt9InitializeParameters*>(buffer);
4141
std::memcpy(m_config_data, params->config_data, ORBIS_AT9_CONFIG_DATA_SIZE);
4242
AjmAt9Decoder::Reset();
43-
m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels * GetPointCodeSize(), 0);
43+
m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels * GetPCMSize(m_format),
44+
0);
4445
}
4546

46-
u8 AjmAt9Decoder::GetPointCodeSize() {
47-
switch (m_format) {
48-
case AjmFormatEncoding::S16:
49-
return sizeof(s16);
50-
case AjmFormatEncoding::S32:
51-
return sizeof(s32);
52-
case AjmFormatEncoding::Float:
53-
return sizeof(float);
54-
default:
55-
UNREACHABLE();
56-
}
57-
}
58-
59-
void AjmAt9Decoder::GetInfo(void* out_info) {
47+
void AjmAt9Decoder::GetInfo(void* out_info) const {
6048
auto* info = reinterpret_cast<AjmSidebandDecAt9CodecInfo*>(out_info);
6149
info->super_frame_size = m_codec_info.superframeSize;
6250
info->frames_in_super_frame = m_codec_info.framesInSuperframe;
@@ -65,8 +53,7 @@ void AjmAt9Decoder::GetInfo(void* out_info) {
6553
}
6654

6755
std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
68-
AjmSidebandGaplessDecode& gapless,
69-
std::optional<u32> max_samples_per_channel) {
56+
AjmInstanceGapless& gapless) {
7057
int ret = 0;
7158
int bytes_used = 0;
7259
switch (m_format) {
@@ -91,32 +78,37 @@ std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
9178

9279
m_superframe_bytes_remain -= bytes_used;
9380

94-
u32 skipped_samples = 0;
95-
if (gapless.skipped_samples < gapless.skip_samples) {
96-
skipped_samples = std::min(u32(m_codec_info.frameSamples),
97-
u32(gapless.skip_samples - gapless.skipped_samples));
98-
gapless.skipped_samples += skipped_samples;
81+
u32 skip_samples = 0;
82+
if (gapless.current.skip_samples > 0) {
83+
skip_samples = std::min(u16(m_codec_info.frameSamples), gapless.current.skip_samples);
84+
gapless.current.skip_samples -= skip_samples;
9985
}
10086

101-
const auto max_samples = max_samples_per_channel.has_value()
102-
? max_samples_per_channel.value() * m_codec_info.channels
103-
: std::numeric_limits<u32>::max();
87+
const auto max_pcm = gapless.init.total_samples != 0
88+
? gapless.current.total_samples * m_codec_info.channels
89+
: std::numeric_limits<u32>::max();
10490

105-
size_t samples_written = 0;
91+
size_t pcm_written = 0;
10692
switch (m_format) {
10793
case AjmFormatEncoding::S16:
108-
samples_written = WriteOutputSamples<s16>(output, skipped_samples, max_samples);
94+
pcm_written = WriteOutputSamples<s16>(output, skip_samples, max_pcm);
10995
break;
11096
case AjmFormatEncoding::S32:
111-
samples_written = WriteOutputSamples<s32>(output, skipped_samples, max_samples);
97+
pcm_written = WriteOutputSamples<s32>(output, skip_samples, max_pcm);
11298
break;
11399
case AjmFormatEncoding::Float:
114-
samples_written = WriteOutputSamples<float>(output, skipped_samples, max_samples);
100+
pcm_written = WriteOutputSamples<float>(output, skip_samples, max_pcm);
115101
break;
116102
default:
117103
UNREACHABLE();
118104
}
119105

106+
const auto samples_written = pcm_written / m_codec_info.channels;
107+
gapless.current.skipped_samples += m_codec_info.frameSamples - samples_written;
108+
if (gapless.init.total_samples != 0) {
109+
gapless.current.total_samples -= samples_written;
110+
}
111+
120112
m_num_frames += 1;
121113
if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) {
122114
if (m_superframe_bytes_remain) {
@@ -126,18 +118,28 @@ std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
126118
m_num_frames = 0;
127119
}
128120

129-
return {1, samples_written / m_codec_info.channels};
121+
return {1, samples_written};
130122
}
131123

132-
AjmSidebandFormat AjmAt9Decoder::GetFormat() {
124+
AjmSidebandFormat AjmAt9Decoder::GetFormat() const {
133125
return AjmSidebandFormat{
134126
.num_channels = u32(m_codec_info.channels),
135127
.channel_mask = GetChannelMask(u32(m_codec_info.channels)),
136128
.sampl_freq = u32(m_codec_info.samplingRate),
137129
.sample_encoding = m_format,
138-
.bitrate = u32(m_codec_info.samplingRate * GetPointCodeSize() * 8),
130+
.bitrate = u32((m_codec_info.samplingRate * m_codec_info.superframeSize * 8) /
131+
(m_codec_info.framesInSuperframe * m_codec_info.frameSamples)),
139132
.reserved = 0,
140133
};
141134
}
142135

136+
u32 AjmAt9Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
137+
const auto max_samples =
138+
gapless.init.total_samples != 0
139+
? std::min(gapless.current.total_samples, u32(m_codec_info.frameSamples))
140+
: m_codec_info.frameSamples;
141+
const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples);
142+
return (max_samples - skip_samples) * m_codec_info.channels * GetPCMSize(m_format);
143+
}
144+
143145
} // namespace Libraries::Ajm

src/core/libraries/ajm/ajm_at9.h

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,13 @@ struct AjmAt9Decoder final : AjmCodec {
3333

3434
void Reset() override;
3535
void Initialize(const void* buffer, u32 buffer_size) override;
36-
void GetInfo(void* out_info) override;
37-
AjmSidebandFormat GetFormat() override;
36+
void GetInfo(void* out_info) const override;
37+
AjmSidebandFormat GetFormat() const override;
38+
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
3839
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
39-
AjmSidebandGaplessDecode& gapless,
40-
std::optional<u32> max_samples) override;
40+
AjmInstanceGapless& gapless) override;
4141

4242
private:
43-
u8 GetPointCodeSize();
44-
4543
template <class T>
4644
size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_samples, u32 max_samples) {
4745
std::span<T> pcm_data{reinterpret_cast<T*>(m_pcm_buffer.data()),

src/core/libraries/ajm/ajm_batch.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
135135
case Identifier::AjmIdentInputControlBuf: {
136136
ASSERT_MSG(!input_control_buffer.has_value(),
137137
"Only one instance of input control buffer is allowed per job");
138-
input_control_buffer = batch_buffer.Consume<AjmChunkBuffer>();
138+
const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
139+
if (buffer.p_address != nullptr && buffer.size != 0) {
140+
input_control_buffer = buffer;
141+
}
139142
break;
140143
}
141144
case Identifier::AjmIdentControlFlags:
@@ -155,19 +158,27 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
155158
case Identifier::AjmIdentInlineBuf: {
156159
ASSERT_MSG(!output_control_buffer.has_value(),
157160
"Only one instance of inline buffer is allowed per job");
158-
inline_buffer = batch_buffer.Consume<AjmChunkBuffer>();
161+
const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
162+
if (buffer.p_address != nullptr && buffer.size != 0) {
163+
inline_buffer = buffer;
164+
}
159165
break;
160166
}
161167
case Identifier::AjmIdentOutputRunBuf: {
162168
auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
163169
u8* p_begin = reinterpret_cast<u8*>(buffer.p_address);
164-
job.output.buffers.emplace_back(std::span<u8>(p_begin, p_begin + buffer.size));
170+
if (p_begin != nullptr && buffer.size != 0) {
171+
job.output.buffers.emplace_back(std::span<u8>(p_begin, p_begin + buffer.size));
172+
}
165173
break;
166174
}
167175
case Identifier::AjmIdentOutputControlBuf: {
168176
ASSERT_MSG(!output_control_buffer.has_value(),
169177
"Only one instance of output control buffer is allowed per job");
170-
output_control_buffer = batch_buffer.Consume<AjmChunkBuffer>();
178+
const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
179+
if (buffer.p_address != nullptr && buffer.size != 0) {
180+
output_control_buffer = buffer;
181+
}
171182
break;
172183
}
173184
default:

src/core/libraries/ajm/ajm_instance.cpp

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
2222
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
2323
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
2424

25+
u8 GetPCMSize(AjmFormatEncoding format) {
26+
switch (format) {
27+
case AjmFormatEncoding::S16:
28+
return sizeof(s16);
29+
case AjmFormatEncoding::S32:
30+
return sizeof(s32);
31+
case AjmFormatEncoding::Float:
32+
return sizeof(float);
33+
default:
34+
UNREACHABLE();
35+
}
36+
}
37+
2538
AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
2639
switch (codec_type) {
2740
case AjmCodecType::At9Dec: {
@@ -30,7 +43,8 @@ AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_fl
3043
break;
3144
}
3245
case AjmCodecType::Mp3Dec: {
33-
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format));
46+
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format),
47+
AjmMp3CodecFlags(flags.codec));
3448
break;
3549
}
3650
default:
@@ -45,7 +59,6 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
4559
m_format = {};
4660
m_gapless = {};
4761
m_resample_parameters = {};
48-
m_gapless_samples = 0;
4962
m_total_samples = 0;
5063
m_codec->Reset();
5164
}
@@ -64,27 +77,47 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
6477
}
6578
if (job.input.gapless_decode.has_value()) {
6679
auto& params = job.input.gapless_decode.value();
67-
m_gapless.total_samples = params.total_samples;
68-
m_gapless.skip_samples = params.skip_samples;
80+
if (params.total_samples != 0) {
81+
const auto max = std::max(params.total_samples, m_gapless.init.total_samples);
82+
m_gapless.current.total_samples += max - m_gapless.init.total_samples;
83+
m_gapless.init.total_samples = max;
84+
}
85+
if (params.skip_samples != 0) {
86+
const auto max = std::max(params.skip_samples, m_gapless.init.skip_samples);
87+
m_gapless.current.skip_samples += max - m_gapless.init.skip_samples;
88+
m_gapless.init.skip_samples = max;
89+
}
6990
}
7091

7192
if (!job.input.buffer.empty() && !job.output.buffers.empty()) {
72-
u32 frames_decoded = 0;
7393
std::span<u8> in_buf(job.input.buffer);
7494
SparseOutputBuffer out_buf(job.output.buffers);
7595

96+
u32 frames_decoded = 0;
7697
auto in_size = in_buf.size();
7798
auto out_size = out_buf.Size();
78-
while (!in_buf.empty() && !out_buf.IsEmpty() && !IsGaplessEnd()) {
79-
const auto samples_remain =
80-
m_gapless.total_samples != 0
81-
? std::optional<u32>{m_gapless.total_samples - m_gapless_samples}
82-
: std::optional<u32>{};
83-
const auto [nframes, nsamples] =
84-
m_codec->ProcessData(in_buf, out_buf, m_gapless, samples_remain);
99+
while (!in_buf.empty() && !out_buf.IsEmpty() && !m_gapless.IsEnd()) {
100+
if (!HasEnoughSpace(out_buf)) {
101+
if (job.output.p_mframe == nullptr || frames_decoded == 0) {
102+
job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
103+
break;
104+
}
105+
}
106+
107+
const auto [nframes, nsamples] = m_codec->ProcessData(in_buf, out_buf, m_gapless);
85108
frames_decoded += nframes;
86109
m_total_samples += nsamples;
87-
m_gapless_samples += nsamples;
110+
111+
if (False(job.flags.run_flags & AjmJobRunFlags::MultipleFrames)) {
112+
break;
113+
}
114+
}
115+
116+
if (m_gapless.IsEnd()) {
117+
in_buf = in_buf.subspan(in_buf.size());
118+
m_gapless.current.total_samples = m_gapless.init.total_samples;
119+
m_gapless.current.skip_samples = m_gapless.init.skip_samples;
120+
m_codec->Reset();
88121
}
89122
if (job.output.p_mframe) {
90123
job.output.p_mframe->num_frames = frames_decoded;
@@ -96,25 +129,19 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
96129
}
97130
}
98131

99-
if (m_flags.gapless_loop && m_gapless.total_samples != 0 &&
100-
m_gapless_samples >= m_gapless.total_samples) {
101-
m_gapless_samples = 0;
102-
m_gapless.skipped_samples = 0;
103-
m_codec->Reset();
104-
}
105132
if (job.output.p_format != nullptr) {
106133
*job.output.p_format = m_codec->GetFormat();
107134
}
108135
if (job.output.p_gapless_decode != nullptr) {
109-
*job.output.p_gapless_decode = m_gapless;
136+
*job.output.p_gapless_decode = m_gapless.current;
110137
}
111138
if (job.output.p_codec_info != nullptr) {
112139
m_codec->GetInfo(job.output.p_codec_info);
113140
}
114141
}
115142

116-
bool AjmInstance::IsGaplessEnd() {
117-
return m_gapless.total_samples != 0 && m_gapless_samples >= m_gapless.total_samples;
143+
bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const {
144+
return output.Size() >= m_codec->GetNextFrameSize(m_gapless);
118145
}
119146

120147
} // namespace Libraries::Ajm

src/core/libraries/ajm/ajm_instance.h

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
namespace Libraries::Ajm {
1616

17+
u8 GetPCMSize(AjmFormatEncoding format);
18+
1719
class SparseOutputBuffer {
1820
public:
1921
SparseOutputBuffer(std::span<std::span<u8>> chunks)
@@ -33,14 +35,17 @@ class SparseOutputBuffer {
3335
++m_current;
3436
}
3537
}
38+
if (!pcm.empty()) {
39+
LOG_ERROR(Lib_Ajm, "Could not write {} samples", pcm.size());
40+
}
3641
return samples_written;
3742
}
3843

39-
bool IsEmpty() {
44+
bool IsEmpty() const {
4045
return m_current == m_chunks.end();
4146
}
4247

43-
size_t Size() {
48+
size_t Size() const {
4449
size_t result = 0;
4550
for (auto it = m_current; it != m_chunks.end(); ++it) {
4651
result += it->size();
@@ -53,17 +58,26 @@ class SparseOutputBuffer {
5358
std::span<std::span<u8>>::iterator m_current;
5459
};
5560

61+
struct AjmInstanceGapless {
62+
AjmSidebandGaplessDecode init{};
63+
AjmSidebandGaplessDecode current{};
64+
65+
bool IsEnd() const {
66+
return init.total_samples != 0 && current.total_samples == 0;
67+
}
68+
};
69+
5670
class AjmCodec {
5771
public:
5872
virtual ~AjmCodec() = default;
5973

6074
virtual void Initialize(const void* buffer, u32 buffer_size) = 0;
6175
virtual void Reset() = 0;
62-
virtual void GetInfo(void* out_info) = 0;
63-
virtual AjmSidebandFormat GetFormat() = 0;
76+
virtual void GetInfo(void* out_info) const = 0;
77+
virtual AjmSidebandFormat GetFormat() const = 0;
78+
virtual u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const = 0;
6479
virtual std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
65-
AjmSidebandGaplessDecode& gapless,
66-
std::optional<u32> max_samples_per_channel) = 0;
80+
AjmInstanceGapless& gapless) = 0;
6781
};
6882

6983
class AjmInstance {
@@ -73,16 +87,14 @@ class AjmInstance {
7387
void ExecuteJob(AjmJob& job);
7488

7589
private:
76-
bool IsGaplessEnd();
90+
bool HasEnoughSpace(const SparseOutputBuffer& output) const;
91+
std::optional<u32> GetNumRemainingSamples() const;
7792

7893
AjmInstanceFlags m_flags{};
7994
AjmSidebandFormat m_format{};
80-
AjmSidebandGaplessDecode m_gapless{};
95+
AjmInstanceGapless m_gapless{};
8196
AjmSidebandResampleParameters m_resample_parameters{};
82-
83-
u32 m_gapless_samples{};
8497
u32 m_total_samples{};
85-
8698
std::unique_ptr<AjmCodec> m_codec;
8799
};
88100

0 commit comments

Comments
 (0)