mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2024-11-23 03:09:55 +00:00
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
This commit is contained in:
parent
8e281575b5
commit
bf239ebc04
@ -40,23 +40,11 @@ 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 * GetPointCodeSize(), 0);
|
||||
m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels * GetPCMSize(m_format),
|
||||
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) const {
|
||||
auto* info = reinterpret_cast<AjmSidebandDecAt9CodecInfo*>(out_info);
|
||||
info->super_frame_size = m_codec_info.superframeSize;
|
||||
info->frames_in_super_frame = m_codec_info.framesInSuperframe;
|
||||
@ -65,8 +53,7 @@ void AjmAt9Decoder::GetInfo(void* out_info) {
|
||||
}
|
||||
|
||||
std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||
AjmSidebandGaplessDecode& gapless,
|
||||
std::optional<u32> max_samples_per_channel) {
|
||||
AjmInstanceGapless& gapless) {
|
||||
int ret = 0;
|
||||
int bytes_used = 0;
|
||||
switch (m_format) {
|
||||
@ -91,32 +78,37 @@ std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
|
||||
|
||||
m_superframe_bytes_remain -= bytes_used;
|
||||
|
||||
u32 skipped_samples = 0;
|
||||
if (gapless.skipped_samples < gapless.skip_samples) {
|
||||
skipped_samples = std::min(u32(m_codec_info.frameSamples),
|
||||
u32(gapless.skip_samples - gapless.skipped_samples));
|
||||
gapless.skipped_samples += skipped_samples;
|
||||
u32 skip_samples = 0;
|
||||
if (gapless.current.skip_samples > 0) {
|
||||
skip_samples = std::min(u16(m_codec_info.frameSamples), gapless.current.skip_samples);
|
||||
gapless.current.skip_samples -= skip_samples;
|
||||
}
|
||||
|
||||
const auto max_samples = max_samples_per_channel.has_value()
|
||||
? max_samples_per_channel.value() * m_codec_info.channels
|
||||
: std::numeric_limits<u32>::max();
|
||||
const auto max_pcm = gapless.init.total_samples != 0
|
||||
? gapless.current.total_samples * m_codec_info.channels
|
||||
: std::numeric_limits<u32>::max();
|
||||
|
||||
size_t samples_written = 0;
|
||||
size_t pcm_written = 0;
|
||||
switch (m_format) {
|
||||
case AjmFormatEncoding::S16:
|
||||
samples_written = WriteOutputSamples<s16>(output, skipped_samples, max_samples);
|
||||
pcm_written = WriteOutputSamples<s16>(output, skip_samples, max_pcm);
|
||||
break;
|
||||
case AjmFormatEncoding::S32:
|
||||
samples_written = WriteOutputSamples<s32>(output, skipped_samples, max_samples);
|
||||
pcm_written = WriteOutputSamples<s32>(output, skip_samples, max_pcm);
|
||||
break;
|
||||
case AjmFormatEncoding::Float:
|
||||
samples_written = WriteOutputSamples<float>(output, skipped_samples, max_samples);
|
||||
pcm_written = WriteOutputSamples<float>(output, skip_samples, max_pcm);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
const auto samples_written = pcm_written / m_codec_info.channels;
|
||||
gapless.current.skipped_samples += m_codec_info.frameSamples - samples_written;
|
||||
if (gapless.init.total_samples != 0) {
|
||||
gapless.current.total_samples -= samples_written;
|
||||
}
|
||||
|
||||
m_num_frames += 1;
|
||||
if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) {
|
||||
if (m_superframe_bytes_remain) {
|
||||
@ -126,18 +118,28 @@ std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
|
||||
m_num_frames = 0;
|
||||
}
|
||||
|
||||
return {1, samples_written / m_codec_info.channels};
|
||||
return {1, samples_written};
|
||||
}
|
||||
|
||||
AjmSidebandFormat AjmAt9Decoder::GetFormat() {
|
||||
AjmSidebandFormat AjmAt9Decoder::GetFormat() const {
|
||||
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),
|
||||
.bitrate = u32((m_codec_info.samplingRate * m_codec_info.superframeSize * 8) /
|
||||
(m_codec_info.framesInSuperframe * m_codec_info.frameSamples)),
|
||||
.reserved = 0,
|
||||
};
|
||||
}
|
||||
|
||||
u32 AjmAt9Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
|
||||
const auto max_samples =
|
||||
gapless.init.total_samples != 0
|
||||
? std::min(gapless.current.total_samples, u32(m_codec_info.frameSamples))
|
||||
: m_codec_info.frameSamples;
|
||||
const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples);
|
||||
return (max_samples - skip_samples) * m_codec_info.channels * GetPCMSize(m_format);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
|
@ -33,15 +33,13 @@ struct AjmAt9Decoder final : AjmCodec {
|
||||
|
||||
void Reset() override;
|
||||
void Initialize(const void* buffer, u32 buffer_size) override;
|
||||
void GetInfo(void* out_info) override;
|
||||
AjmSidebandFormat GetFormat() override;
|
||||
void GetInfo(void* out_info) const override;
|
||||
AjmSidebandFormat GetFormat() const override;
|
||||
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
|
||||
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmSidebandGaplessDecode& gapless,
|
||||
std::optional<u32> max_samples) override;
|
||||
AjmInstanceGapless& gapless) 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()),
|
||||
|
@ -135,7 +135,10 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
|
||||
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>();
|
||||
const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
|
||||
if (buffer.p_address != nullptr && buffer.size != 0) {
|
||||
input_control_buffer = buffer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Identifier::AjmIdentControlFlags:
|
||||
@ -155,19 +158,27 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
|
||||
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>();
|
||||
const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
|
||||
if (buffer.p_address != nullptr && buffer.size != 0) {
|
||||
inline_buffer = buffer;
|
||||
}
|
||||
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));
|
||||
if (p_begin != nullptr && buffer.size != 0) {
|
||||
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>();
|
||||
const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
|
||||
if (buffer.p_address != nullptr && buffer.size != 0) {
|
||||
output_control_buffer = buffer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -22,6 +22,19 @@ constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
|
||||
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
|
||||
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
||||
|
||||
u8 GetPCMSize(AjmFormatEncoding format) {
|
||||
switch (format) {
|
||||
case AjmFormatEncoding::S16:
|
||||
return sizeof(s16);
|
||||
case AjmFormatEncoding::S32:
|
||||
return sizeof(s32);
|
||||
case AjmFormatEncoding::Float:
|
||||
return sizeof(float);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
|
||||
switch (codec_type) {
|
||||
case AjmCodecType::At9Dec: {
|
||||
@ -30,7 +43,8 @@ AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_fl
|
||||
break;
|
||||
}
|
||||
case AjmCodecType::Mp3Dec: {
|
||||
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format));
|
||||
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format),
|
||||
AjmMp3CodecFlags(flags.codec));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -45,7 +59,6 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
m_format = {};
|
||||
m_gapless = {};
|
||||
m_resample_parameters = {};
|
||||
m_gapless_samples = 0;
|
||||
m_total_samples = 0;
|
||||
m_codec->Reset();
|
||||
}
|
||||
@ -64,27 +77,47 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
}
|
||||
if (job.input.gapless_decode.has_value()) {
|
||||
auto& params = job.input.gapless_decode.value();
|
||||
m_gapless.total_samples = params.total_samples;
|
||||
m_gapless.skip_samples = params.skip_samples;
|
||||
if (params.total_samples != 0) {
|
||||
const auto max = std::max(params.total_samples, m_gapless.init.total_samples);
|
||||
m_gapless.current.total_samples += max - m_gapless.init.total_samples;
|
||||
m_gapless.init.total_samples = max;
|
||||
}
|
||||
if (params.skip_samples != 0) {
|
||||
const auto max = std::max(params.skip_samples, m_gapless.init.skip_samples);
|
||||
m_gapless.current.skip_samples += max - m_gapless.init.skip_samples;
|
||||
m_gapless.init.skip_samples = max;
|
||||
}
|
||||
}
|
||||
|
||||
if (!job.input.buffer.empty() && !job.output.buffers.empty()) {
|
||||
u32 frames_decoded = 0;
|
||||
std::span<u8> in_buf(job.input.buffer);
|
||||
SparseOutputBuffer out_buf(job.output.buffers);
|
||||
|
||||
u32 frames_decoded = 0;
|
||||
auto in_size = in_buf.size();
|
||||
auto out_size = out_buf.Size();
|
||||
while (!in_buf.empty() && !out_buf.IsEmpty() && !IsGaplessEnd()) {
|
||||
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);
|
||||
while (!in_buf.empty() && !out_buf.IsEmpty() && !m_gapless.IsEnd()) {
|
||||
if (!HasEnoughSpace(out_buf)) {
|
||||
if (job.output.p_mframe == nullptr || frames_decoded == 0) {
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto [nframes, nsamples] = m_codec->ProcessData(in_buf, out_buf, m_gapless);
|
||||
frames_decoded += nframes;
|
||||
m_total_samples += nsamples;
|
||||
m_gapless_samples += nsamples;
|
||||
|
||||
if (False(job.flags.run_flags & AjmJobRunFlags::MultipleFrames)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_gapless.IsEnd()) {
|
||||
in_buf = in_buf.subspan(in_buf.size());
|
||||
m_gapless.current.total_samples = m_gapless.init.total_samples;
|
||||
m_gapless.current.skip_samples = m_gapless.init.skip_samples;
|
||||
m_codec->Reset();
|
||||
}
|
||||
if (job.output.p_mframe) {
|
||||
job.output.p_mframe->num_frames = frames_decoded;
|
||||
@ -96,25 +129,19 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
}
|
||||
}
|
||||
|
||||
if (m_flags.gapless_loop && m_gapless.total_samples != 0 &&
|
||||
m_gapless_samples >= m_gapless.total_samples) {
|
||||
m_gapless_samples = 0;
|
||||
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;
|
||||
*job.output.p_gapless_decode = m_gapless.current;
|
||||
}
|
||||
if (job.output.p_codec_info != nullptr) {
|
||||
m_codec->GetInfo(job.output.p_codec_info);
|
||||
}
|
||||
}
|
||||
|
||||
bool AjmInstance::IsGaplessEnd() {
|
||||
return m_gapless.total_samples != 0 && m_gapless_samples >= m_gapless.total_samples;
|
||||
bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const {
|
||||
return output.Size() >= m_codec->GetNextFrameSize(m_gapless);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
u8 GetPCMSize(AjmFormatEncoding format);
|
||||
|
||||
class SparseOutputBuffer {
|
||||
public:
|
||||
SparseOutputBuffer(std::span<std::span<u8>> chunks)
|
||||
@ -33,14 +35,17 @@ public:
|
||||
++m_current;
|
||||
}
|
||||
}
|
||||
if (!pcm.empty()) {
|
||||
LOG_ERROR(Lib_Ajm, "Could not write {} samples", pcm.size());
|
||||
}
|
||||
return samples_written;
|
||||
}
|
||||
|
||||
bool IsEmpty() {
|
||||
bool IsEmpty() const {
|
||||
return m_current == m_chunks.end();
|
||||
}
|
||||
|
||||
size_t Size() {
|
||||
size_t Size() const {
|
||||
size_t result = 0;
|
||||
for (auto it = m_current; it != m_chunks.end(); ++it) {
|
||||
result += it->size();
|
||||
@ -53,17 +58,26 @@ private:
|
||||
std::span<std::span<u8>>::iterator m_current;
|
||||
};
|
||||
|
||||
struct AjmInstanceGapless {
|
||||
AjmSidebandGaplessDecode init{};
|
||||
AjmSidebandGaplessDecode current{};
|
||||
|
||||
bool IsEnd() const {
|
||||
return init.total_samples != 0 && current.total_samples == 0;
|
||||
}
|
||||
};
|
||||
|
||||
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 void GetInfo(void* out_info) const = 0;
|
||||
virtual AjmSidebandFormat GetFormat() const = 0;
|
||||
virtual u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const = 0;
|
||||
virtual std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmSidebandGaplessDecode& gapless,
|
||||
std::optional<u32> max_samples_per_channel) = 0;
|
||||
AjmInstanceGapless& gapless) = 0;
|
||||
};
|
||||
|
||||
class AjmInstance {
|
||||
@ -73,16 +87,14 @@ public:
|
||||
void ExecuteJob(AjmJob& job);
|
||||
|
||||
private:
|
||||
bool IsGaplessEnd();
|
||||
bool HasEnoughSpace(const SparseOutputBuffer& output) const;
|
||||
std::optional<u32> GetNumRemainingSamples() const;
|
||||
|
||||
AjmInstanceFlags m_flags{};
|
||||
AjmSidebandFormat m_format{};
|
||||
AjmSidebandGaplessDecode m_gapless{};
|
||||
AjmInstanceGapless m_gapless{};
|
||||
AjmSidebandResampleParameters m_resample_parameters{};
|
||||
|
||||
u32 m_gapless_samples{};
|
||||
u32 m_total_samples{};
|
||||
|
||||
std::unique_ptr<AjmCodec> m_codec;
|
||||
};
|
||||
|
||||
|
@ -15,18 +15,50 @@ extern "C" {
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
// Following tables have been reversed from AJM library
|
||||
static constexpr std::array<std::array<s32, 3>, 3> SamplerateTable = {{
|
||||
{0x5622, 0x5DC0, 0x3E80},
|
||||
{0xAC44, 0xBB80, 0x7D00},
|
||||
{0x2B11, 0x2EE0, 0x1F40},
|
||||
}};
|
||||
static constexpr std::array<std::array<s32, 4>, 4> Mp3SampleRateTable = {
|
||||
std::array<s32, 4>{11025, 12000, 8000, 0},
|
||||
std::array<s32, 4>{0, 0, 0, 0},
|
||||
std::array<s32, 4>{22050, 24000, 16000, 0},
|
||||
std::array<s32, 4>{44100, 48000, 32000, 0},
|
||||
};
|
||||
|
||||
static constexpr std::array<std::array<s32, 15>, 2> BitrateTable = {{
|
||||
{0, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0xA0, 0xC0, 0xE0, 0x100, 0x140},
|
||||
{0, 0x8, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0},
|
||||
}};
|
||||
static constexpr std::array<std::array<s32, 16>, 4> Mp3BitRateTable = {
|
||||
std::array<s32, 16>{0, 8, 16, 24, 32, 40, 48, 56, 64, 0, 0, 0, 0, 0, 0, 0},
|
||||
std::array<s32, 16>{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
std::array<s32, 16>{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0},
|
||||
std::array<s32, 16>{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0},
|
||||
};
|
||||
|
||||
static constexpr std::array<s32, 2> UnkTable = {0x48, 0x90};
|
||||
enum class Mp3AudioVersion : u32 {
|
||||
V2_5 = 0,
|
||||
Reserved = 1,
|
||||
V2 = 2,
|
||||
V1 = 3,
|
||||
};
|
||||
|
||||
enum class Mp3ChannelMode : u32 {
|
||||
Stereo = 0,
|
||||
JointStereo = 1,
|
||||
DualChannel = 2,
|
||||
SingleChannel = 3,
|
||||
};
|
||||
|
||||
struct Mp3Header {
|
||||
u32 emphasis : 2;
|
||||
u32 original : 1;
|
||||
u32 copyright : 1;
|
||||
u32 mode_ext_idx : 2;
|
||||
Mp3ChannelMode channel_mode : 2;
|
||||
u32 : 1;
|
||||
u32 padding : 1;
|
||||
u32 sampling_rate_idx : 2;
|
||||
u32 bitrate_idx : 4;
|
||||
u32 protection_type : 1;
|
||||
u32 layer_type : 2;
|
||||
Mp3AudioVersion version : 2;
|
||||
u32 sync : 11;
|
||||
};
|
||||
static_assert(sizeof(Mp3Header) == sizeof(u32));
|
||||
|
||||
static AVSampleFormat AjmToAVSampleFormat(AjmFormatEncoding format) {
|
||||
switch (format) {
|
||||
@ -62,7 +94,7 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) {
|
||||
swr_init(m_swr_context);
|
||||
const auto res = swr_convert_frame(m_swr_context, new_frame, frame);
|
||||
if (res < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not convert to S16: {}", av_err2str(res));
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not convert frame: {}", av_err2str(res));
|
||||
av_frame_free(&new_frame);
|
||||
av_frame_free(&frame);
|
||||
return nullptr;
|
||||
@ -71,48 +103,57 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) {
|
||||
return new_frame;
|
||||
}
|
||||
|
||||
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format)
|
||||
: m_format(format), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)),
|
||||
m_parser(av_parser_init(m_codec->id)) {
|
||||
AjmMp3Decoder::Reset();
|
||||
}
|
||||
|
||||
AjmMp3Decoder::~AjmMp3Decoder() {
|
||||
swr_free(&m_swr_context);
|
||||
avcodec_free_context(&m_codec_context);
|
||||
}
|
||||
|
||||
void AjmMp3Decoder::Reset() {
|
||||
if (m_codec_context) {
|
||||
avcodec_free_context(&m_codec_context);
|
||||
}
|
||||
m_codec_context = avcodec_alloc_context3(m_codec);
|
||||
ASSERT_MSG(m_codec_context, "Could not allocate audio m_codec context");
|
||||
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags)
|
||||
: m_format(format), m_flags(flags), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)),
|
||||
m_codec_context(avcodec_alloc_context3(m_codec)), m_parser(av_parser_init(m_codec->id)) {
|
||||
int ret = avcodec_open2(m_codec_context, m_codec, nullptr);
|
||||
ASSERT_MSG(ret >= 0, "Could not open m_codec");
|
||||
}
|
||||
|
||||
void AjmMp3Decoder::GetInfo(void* out_info) {
|
||||
AjmMp3Decoder::~AjmMp3Decoder() {
|
||||
swr_free(&m_swr_context);
|
||||
av_parser_close(m_parser);
|
||||
avcodec_free_context(&m_codec_context);
|
||||
}
|
||||
|
||||
void AjmMp3Decoder::Reset() {
|
||||
avcodec_flush_buffers(m_codec_context);
|
||||
m_header.reset();
|
||||
m_frame_samples = 0;
|
||||
}
|
||||
|
||||
void AjmMp3Decoder::GetInfo(void* out_info) const {
|
||||
auto* info = reinterpret_cast<AjmSidebandDecMp3CodecInfo*>(out_info);
|
||||
if (m_header.has_value()) {
|
||||
auto* header = reinterpret_cast<const Mp3Header*>(&m_header.value());
|
||||
info->header = std::byteswap(m_header.value());
|
||||
info->has_crc = header->protection_type;
|
||||
info->channel_mode = static_cast<ChannelMode>(header->channel_mode);
|
||||
info->mode_extension = header->mode_ext_idx;
|
||||
info->copyright = header->copyright;
|
||||
info->original = header->original;
|
||||
info->emphasis = header->emphasis;
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||
AjmSidebandGaplessDecode& gapless,
|
||||
std::optional<u32> max_samples_per_channel) {
|
||||
AjmInstanceGapless& gapless) {
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
|
||||
if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) {
|
||||
m_header = std::byteswap(*reinterpret_cast<u32*>(in_buf.data()));
|
||||
AjmDecMp3ParseFrame info{};
|
||||
ParseMp3Header(in_buf.data(), in_buf.size(), false, &info);
|
||||
m_frame_samples = info.samples_per_channel;
|
||||
}
|
||||
|
||||
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
|
||||
in_buf.size(), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
|
||||
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
|
||||
in_buf = in_buf.subspan(ret);
|
||||
|
||||
u32 frames_decoded = 0;
|
||||
u32 samples_decoded = 0;
|
||||
|
||||
auto max_samples =
|
||||
max_samples_per_channel.has_value()
|
||||
? max_samples_per_channel.value() * m_codec_context->ch_layout.nb_channels
|
||||
: std::numeric_limits<u32>::max();
|
||||
u32 samples_written = 0;
|
||||
|
||||
if (pkt->size) {
|
||||
// Send the packet with the compressed data to the decoder
|
||||
@ -135,31 +176,38 @@ std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
|
||||
frame = ConvertAudioFrame(frame);
|
||||
|
||||
frames_decoded += 1;
|
||||
u32 skipped_samples = 0;
|
||||
if (gapless.skipped_samples < gapless.skip_samples) {
|
||||
skipped_samples = std::min(u32(frame->nb_samples),
|
||||
u32(gapless.skip_samples - gapless.skipped_samples));
|
||||
gapless.skipped_samples += skipped_samples;
|
||||
u32 skip_samples = 0;
|
||||
if (gapless.current.skip_samples > 0) {
|
||||
skip_samples = std::min(u16(frame->nb_samples), gapless.current.skip_samples);
|
||||
gapless.current.skip_samples -= skip_samples;
|
||||
}
|
||||
|
||||
const auto max_pcm =
|
||||
gapless.init.total_samples != 0
|
||||
? gapless.current.total_samples * m_codec_context->ch_layout.nb_channels
|
||||
: std::numeric_limits<u32>::max();
|
||||
|
||||
u32 pcm_written = 0;
|
||||
switch (m_format) {
|
||||
case AjmFormatEncoding::S16:
|
||||
samples_decoded +=
|
||||
WriteOutputSamples<s16>(frame, output, skipped_samples, max_samples);
|
||||
pcm_written = WriteOutputPCM<s16>(frame, output, skip_samples, max_pcm);
|
||||
break;
|
||||
case AjmFormatEncoding::S32:
|
||||
samples_decoded +=
|
||||
WriteOutputSamples<s32>(frame, output, skipped_samples, max_samples);
|
||||
pcm_written = WriteOutputPCM<s32>(frame, output, skip_samples, max_pcm);
|
||||
break;
|
||||
case AjmFormatEncoding::Float:
|
||||
samples_decoded +=
|
||||
WriteOutputSamples<float>(frame, output, skipped_samples, max_samples);
|
||||
pcm_written = WriteOutputPCM<float>(frame, output, skip_samples, max_pcm);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
max_samples -= samples_decoded;
|
||||
const auto samples = pcm_written / m_codec_context->ch_layout.nb_channels;
|
||||
samples_written += samples;
|
||||
gapless.current.skipped_samples += frame->nb_samples - samples;
|
||||
if (gapless.init.total_samples != 0) {
|
||||
gapless.current.total_samples -= samples;
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
@ -167,38 +215,221 @@ std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
|
||||
|
||||
av_packet_free(&pkt);
|
||||
|
||||
return {frames_decoded, samples_decoded};
|
||||
return {frames_decoded, samples_written};
|
||||
}
|
||||
|
||||
int AjmMp3Decoder::ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
||||
u32 AjmMp3Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
|
||||
const auto max_samples = gapless.init.total_samples != 0
|
||||
? std::min(gapless.current.total_samples, m_frame_samples)
|
||||
: m_frame_samples;
|
||||
const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples);
|
||||
return (max_samples - skip_samples) * m_codec_context->ch_layout.nb_channels *
|
||||
GetPCMSize(m_format);
|
||||
}
|
||||
|
||||
class BitReader {
|
||||
public:
|
||||
BitReader(const u8* data) : m_data(data) {}
|
||||
|
||||
template <class T>
|
||||
T Read(u32 const nbits) {
|
||||
T accumulator = 0;
|
||||
for (unsigned i = 0; i < nbits; ++i) {
|
||||
accumulator = (accumulator << 1) + GetBit();
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
void Skip(size_t nbits) {
|
||||
m_bit_offset += nbits;
|
||||
}
|
||||
|
||||
size_t GetCurrentOffset() {
|
||||
return m_bit_offset;
|
||||
}
|
||||
|
||||
private:
|
||||
u8 GetBit() {
|
||||
const auto bit = (m_data[m_bit_offset / 8] >> (7 - (m_bit_offset % 8))) & 1;
|
||||
m_bit_offset += 1;
|
||||
return bit;
|
||||
}
|
||||
|
||||
const u8* m_data;
|
||||
size_t m_bit_offset = 0;
|
||||
};
|
||||
|
||||
int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ofl,
|
||||
AjmDecMp3ParseFrame* frame) {
|
||||
LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
|
||||
if (buf == nullptr || stream_size < 4 || frame == nullptr) {
|
||||
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
if ((buf[0] & SYNCWORDH) != SYNCWORDH || (buf[1] & SYNCWORDL) != SYNCWORDL) {
|
||||
|
||||
if (p_begin == nullptr || stream_size < 4 || frame == nullptr) {
|
||||
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const u32 unk_idx = buf[1] >> 3 & 1;
|
||||
const s32 version_idx = (buf[1] >> 3 & 3) ^ 2;
|
||||
const s32 sr_idx = buf[2] >> 2 & 3;
|
||||
const s32 br_idx = (buf[2] >> 4) & 0xf;
|
||||
const s32 padding_bit = (buf[2] >> 1) & 0x1;
|
||||
const auto* p_current = p_begin;
|
||||
|
||||
auto bytes = std::byteswap(*reinterpret_cast<const u32*>(p_current));
|
||||
p_current += 4;
|
||||
auto header = reinterpret_cast<const Mp3Header*>(&bytes);
|
||||
if (header->sync != 0x7FF) {
|
||||
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
frame->sample_rate = Mp3SampleRateTable[u32(header->version)][header->sampling_rate_idx];
|
||||
frame->bitrate = Mp3BitRateTable[u32(header->version)][header->bitrate_idx] * 1000;
|
||||
frame->num_channels = header->channel_mode == Mp3ChannelMode::SingleChannel ? 1 : 2;
|
||||
if (header->version == Mp3AudioVersion::V1) {
|
||||
frame->frame_size = (144 * frame->bitrate) / frame->sample_rate + header->padding;
|
||||
frame->samples_per_channel = 1152;
|
||||
} else {
|
||||
frame->frame_size = (72 * frame->bitrate) / frame->sample_rate + header->padding;
|
||||
frame->samples_per_channel = 576;
|
||||
}
|
||||
|
||||
frame->sample_rate = SamplerateTable[version_idx][sr_idx];
|
||||
frame->bitrate = BitrateTable[version_idx != 1][br_idx] * 1000;
|
||||
frame->num_channels = (buf[3] < 0xc0) + 1;
|
||||
frame->frame_size = (UnkTable[unk_idx] * frame->bitrate) / frame->sample_rate + padding_bit;
|
||||
frame->samples_per_channel = UnkTable[unk_idx] * 8;
|
||||
frame->encoder_delay = 0;
|
||||
frame->num_frames = 0;
|
||||
frame->total_samples = 0;
|
||||
frame->ofl_type = AjmDecMp3OflType::None;
|
||||
|
||||
if (!parse_ofl) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
BitReader reader(p_current);
|
||||
if (header->protection_type == 0) {
|
||||
reader.Skip(16); // crc = reader.Read<u16>(16);
|
||||
}
|
||||
|
||||
if (header->version == Mp3AudioVersion::V1) {
|
||||
// main_data_begin = reader.Read<u16>(9);
|
||||
// if (header->channel_mode == Mp3ChannelMode::SingleChannel) {
|
||||
// private_bits = reader.Read<u8>(5);
|
||||
// } else {
|
||||
// private_bits = reader.Read<u8>(3);
|
||||
// }
|
||||
// for (u32 ch = 0; ch < frame->num_channels; ++ch) {
|
||||
// for (u8 band = 0; band < 4; ++band) {
|
||||
// scfsi[ch][band] = reader.Read<bool>(1);
|
||||
// }
|
||||
// }
|
||||
if (header->channel_mode == Mp3ChannelMode::SingleChannel) {
|
||||
reader.Skip(18);
|
||||
} else {
|
||||
reader.Skip(20);
|
||||
}
|
||||
} else {
|
||||
// main_data_begin = reader.Read<u16>(8);
|
||||
// if (header->channel_mode == Mp3ChannelMode::SingleChannel) {
|
||||
// private_bits = reader.Read<u8>(1);
|
||||
// } else {
|
||||
// private_bits = reader.Read<u8>(2);
|
||||
// }
|
||||
if (header->channel_mode == Mp3ChannelMode::SingleChannel) {
|
||||
reader.Skip(9);
|
||||
} else {
|
||||
reader.Skip(10);
|
||||
}
|
||||
}
|
||||
|
||||
u32 part2_3_length = 0;
|
||||
// Number of granules (18x32 sub-band samples)
|
||||
const u8 ngr = header->version == Mp3AudioVersion::V1 ? 2 : 1;
|
||||
for (u8 gr = 0; gr < ngr; ++gr) {
|
||||
for (u32 ch = 0; ch < frame->num_channels; ++ch) {
|
||||
// part2_3_length[gr][ch] = reader.Read<u16>(12);
|
||||
part2_3_length += reader.Read<u16>(12);
|
||||
// big_values[gr][ch] = reader.Read<u16>(9);
|
||||
// global_main[gr][ch] = reader.Read<u8>(8);
|
||||
// if (header->version == Mp3AudioVersion::V1) {
|
||||
// scalefac_compress[gr][ch] = reader.Read<u16>(4);
|
||||
// } else {
|
||||
// scalefac_compress[gr][ch] = reader.Read<u16>(9);
|
||||
// }
|
||||
// window_switching_flag = reader.Read<bool>(1);
|
||||
// if (window_switching_flag) {
|
||||
// block_type[gr][ch] = reader.Read<u8>(2);
|
||||
// mixed_block_flag[gr][ch] = reader.Read<bool>(1);
|
||||
// for (u8 region = 0; region < 2; ++region) {
|
||||
// table_select[gr][ch][region] = reader.Read<u8>(5);
|
||||
// }
|
||||
// for (u8 window = 0; window < 3; ++window) {
|
||||
// subblock_gain[gr][ch][window] = reader.Read<u8>(3);
|
||||
// }
|
||||
// } else {
|
||||
// for (u8 region = 0; region < 3; ++region) {
|
||||
// table_select[gr][ch][region] = reader.Read<u8>(5);
|
||||
// }
|
||||
// region0_count[gr][ch] = reader.Read<u8>(4);
|
||||
// region1_count[gr][ch] = reader.Read<u8>(3);
|
||||
// }
|
||||
// if (header->version == Mp3AudioVersion::V1) {
|
||||
// preflag[gr][ch] = reader.Read<bool>(1);
|
||||
// }
|
||||
// scalefac_scale[gr][ch] = reader.Read<bool>(1);
|
||||
// count1table_select[gr][ch] = reader.Read<bool>(1);
|
||||
if (header->version == Mp3AudioVersion::V1) {
|
||||
reader.Skip(47);
|
||||
} else {
|
||||
reader.Skip(51);
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.Skip(part2_3_length);
|
||||
|
||||
p_current += ((reader.GetCurrentOffset() + 7) / 8);
|
||||
|
||||
const auto* p_end = p_begin + frame->frame_size;
|
||||
if (memcmp(p_current, "Xing", 4) == 0 || memcmp(p_current, "Info", 4) == 0) {
|
||||
// TODO: Parse Xing/Lame header
|
||||
LOG_ERROR(Lib_Ajm, "Xing/Lame header is not implemented.");
|
||||
} else if (memcmp(p_current, "VBRI", 4) == 0) {
|
||||
// TODO: Parse VBRI header
|
||||
LOG_ERROR(Lib_Ajm, "VBRI header is not implemented.");
|
||||
} else {
|
||||
// Parse FGH header
|
||||
constexpr auto fgh_indicator = 0xB4;
|
||||
while ((p_current + 9) < p_end && *p_current != fgh_indicator) {
|
||||
++p_current;
|
||||
}
|
||||
auto p_fgh = p_current;
|
||||
if ((p_current + 9) < p_end && *p_current == fgh_indicator) {
|
||||
u8 crc = 0xFF;
|
||||
auto crc_func = [](u8 c, u8 v, u8 s) {
|
||||
if (((c >> 7) & 1) != ((v >> s) & 1)) {
|
||||
return c * 2;
|
||||
}
|
||||
return (c * 2) ^ 0x45;
|
||||
};
|
||||
for (u8 i = 0; i < 9; ++i, ++p_current) {
|
||||
for (u8 j = 0; j < 8; ++j) {
|
||||
crc = crc_func(crc, *p_current, 7 - j);
|
||||
}
|
||||
}
|
||||
if (p_fgh[9] == crc) {
|
||||
frame->encoder_delay = std::byteswap(*reinterpret_cast<const u16*>(p_fgh + 1));
|
||||
frame->total_samples = std::byteswap(*reinterpret_cast<const u32*>(p_fgh + 3));
|
||||
frame->ofl_type = AjmDecMp3OflType::Fgh;
|
||||
} else {
|
||||
LOG_ERROR(Lib_Ajm, "FGH header CRC is incorrect.");
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Lib_Ajm, "Could not find vendor header.");
|
||||
}
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
AjmSidebandFormat AjmMp3Decoder::GetFormat() {
|
||||
LOG_ERROR(Lib_Ajm, "Unimplemented");
|
||||
return AjmSidebandFormat{};
|
||||
AjmSidebandFormat AjmMp3Decoder::GetFormat() const {
|
||||
return AjmSidebandFormat{
|
||||
.num_channels = u32(m_codec_context->ch_layout.nb_channels),
|
||||
.channel_mask = GetChannelMask(u32(m_codec_context->ch_layout.nb_channels)),
|
||||
.sampl_freq = u32(m_codec_context->sample_rate),
|
||||
.sample_encoding = m_format,
|
||||
.bitrate = u32(m_codec_context->bit_rate),
|
||||
.reserved = 0,
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
|
@ -13,7 +13,19 @@ struct SwrContext;
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
enum class AjmDecMp3OflType : u32 { None = 0, Lame = 1, Vbri = 2, Fgh = 3, VbriAndFgh = 4 };
|
||||
enum class AjmDecMp3OflType : u32 {
|
||||
None = 0,
|
||||
Lame = 1,
|
||||
Vbri = 2,
|
||||
Fgh = 3,
|
||||
VbriAndFgh = 4,
|
||||
};
|
||||
|
||||
enum AjmMp3CodecFlags : u32 {
|
||||
IgnoreOfl = 1 << 0,
|
||||
VlcRewind = 1 << 8,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AjmMp3CodecFlags)
|
||||
|
||||
// 11-bit syncword if MPEG 2.5 extensions are enabled
|
||||
static constexpr u8 SYNCWORDH = 0xff;
|
||||
@ -51,39 +63,40 @@ struct AjmSidebandDecMp3CodecInfo {
|
||||
|
||||
class AjmMp3Decoder : public AjmCodec {
|
||||
public:
|
||||
explicit AjmMp3Decoder(AjmFormatEncoding format);
|
||||
explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags);
|
||||
~AjmMp3Decoder() override;
|
||||
|
||||
void Reset() override;
|
||||
void Initialize(const void* buffer, u32 buffer_size) override {}
|
||||
void GetInfo(void* out_info) override;
|
||||
AjmSidebandFormat GetFormat() override;
|
||||
void GetInfo(void* out_info) const override;
|
||||
AjmSidebandFormat GetFormat() const override;
|
||||
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
|
||||
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmSidebandGaplessDecode& gapless,
|
||||
std::optional<u32> max_samples_per_channel) override;
|
||||
AjmInstanceGapless& gapless) override;
|
||||
|
||||
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
||||
AjmDecMp3ParseFrame* frame);
|
||||
|
||||
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);
|
||||
size_t WriteOutputPCM(AVFrame* frame, SparseOutputBuffer& output, u32 skipped_samples,
|
||||
u32 max_pcm) {
|
||||
std::span<T> pcm_data(reinterpret_cast<T*>(frame->data[0]),
|
||||
frame->nb_samples * frame->ch_layout.nb_channels);
|
||||
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;
|
||||
return output.Write(pcm_data.subspan(0, std::min(u32(pcm_data.size()), max_pcm)));
|
||||
}
|
||||
|
||||
AVFrame* ConvertAudioFrame(AVFrame* frame);
|
||||
|
||||
const AjmFormatEncoding m_format;
|
||||
const AjmMp3CodecFlags m_flags;
|
||||
const AVCodec* m_codec = nullptr;
|
||||
AVCodecContext* m_codec_context = nullptr;
|
||||
AVCodecParserContext* m_parser = nullptr;
|
||||
SwrContext* m_swr_context = nullptr;
|
||||
std::optional<u32> m_header;
|
||||
u32 m_frame_samples = 0;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
|
Loading…
Reference in New Issue
Block a user