mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 03:16:54 +02:00
Initial commit
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "libs/ajm/ffmpeg_decoder_common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace Libs::Audio::Ajm {
|
||||
|
||||
struct AjmDecM4aacInitializeParameters {
|
||||
uint32_t config_number;
|
||||
uint32_t sampling_freq_index;
|
||||
};
|
||||
|
||||
struct AjmSidebandDecM4aacCodecInfo {
|
||||
uint32_t heaac;
|
||||
uint32_t reserved;
|
||||
};
|
||||
|
||||
static_assert(sizeof(AjmDecM4aacInitializeParameters) == 8);
|
||||
static_assert(sizeof(AjmSidebandDecM4aacCodecInfo) == 8);
|
||||
|
||||
static constexpr uint64_t AJM_INSTANCE_FLAG_DEC_M4AAC_ENABLE_SBR_DECODE =
|
||||
1ull << (AJM_INSTANCE_FLAG_CODEC_OFFSET + 0u);
|
||||
static constexpr uint64_t AJM_INSTANCE_FLAG_DEC_M4AAC_ENABLE_NONDELAY_OUTPUT =
|
||||
1ull << (AJM_INSTANCE_FLAG_CODEC_OFFSET + 1u);
|
||||
static constexpr uint32_t AJM_DEC_M4AAC_CONFIG_NUMBER_ADTS = 1;
|
||||
static constexpr uint32_t AJM_DEC_M4AAC_CONFIG_NUMBER_RAW = 2;
|
||||
static constexpr uint32_t AJM_DEC_M4AAC_CONFIG_NUMBER_SAF = 3;
|
||||
static constexpr uint32_t AJM_DEC_M4AAC_MAX_CHANNELS = 8;
|
||||
static constexpr uint32_t AJM_DEC_M4AAC_MAX_SAMPLING_FREQ_INDEX = 11;
|
||||
|
||||
class AjmAacDecoder final: public AjmDecoder {
|
||||
public:
|
||||
AjmAacDecoder(uint32_t channels, uint32_t sample_rate, AjmSampleEncoding encoding,
|
||||
uint64_t flags)
|
||||
: AjmDecoder(channels, sample_rate, encoding), m_flags(flags) {
|
||||
m_codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
|
||||
if (m_codec == nullptr) {
|
||||
LOGF("AJM AAC: avcodec_find_decoder failed\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
~AjmAacDecoder() override { Release(); }
|
||||
|
||||
AjmDecodeResult Initialize(const void* codec_parameters,
|
||||
size_t codec_parameters_size) override {
|
||||
auto result = MakeResult();
|
||||
|
||||
if (codec_parameters == nullptr ||
|
||||
codec_parameters_size < sizeof(AjmDecM4aacInitializeParameters)) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER;
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto* params = static_cast<const AjmDecM4aacInitializeParameters*>(codec_parameters);
|
||||
if (params->config_number != AJM_DEC_M4AAC_CONFIG_NUMBER_ADTS &&
|
||||
params->config_number != AJM_DEC_M4AAC_CONFIG_NUMBER_RAW &&
|
||||
params->config_number != AJM_DEC_M4AAC_CONFIG_NUMBER_SAF) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER;
|
||||
return result;
|
||||
}
|
||||
if (params->config_number == AJM_DEC_M4AAC_CONFIG_NUMBER_RAW &&
|
||||
params->sampling_freq_index > AJM_DEC_M4AAC_MAX_SAMPLING_FREQ_INDEX) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER;
|
||||
return result;
|
||||
}
|
||||
|
||||
m_config_number = params->config_number;
|
||||
m_sampling_freq_index = params->sampling_freq_index;
|
||||
m_is_initialized = true;
|
||||
Reset();
|
||||
if (m_codec_context == nullptr) {
|
||||
m_is_initialized = false;
|
||||
result.result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return result;
|
||||
}
|
||||
|
||||
LOGF("AJM AAC initialized: config=%" PRIu32 ", freq_index=%" PRIu32 "\n", m_config_number,
|
||||
m_sampling_freq_index);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Reset() override {
|
||||
Release();
|
||||
m_skip_frames =
|
||||
((m_flags & AJM_INSTANCE_FLAG_DEC_M4AAC_ENABLE_NONDELAY_OUTPUT) != 0 ? 0u : 2u);
|
||||
m_total_decoded_samples = 0;
|
||||
m_bitrate = 0;
|
||||
if (m_is_initialized) {
|
||||
OpenDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
AjmDecodeResult Decode(const void* input, size_t input_size, void* output, size_t output_size,
|
||||
bool multiple_frames, AjmGaplessState* gapless) override {
|
||||
(void)multiple_frames;
|
||||
auto result = MakeResult();
|
||||
|
||||
if (!m_is_initialized) {
|
||||
result.result = AJM_RESULT_NOT_INITIALIZED;
|
||||
return result;
|
||||
}
|
||||
if (m_codec_context == nullptr) {
|
||||
result.result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return result;
|
||||
}
|
||||
if (input == nullptr || input_size == 0) {
|
||||
result.result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return result;
|
||||
}
|
||||
if (input_size > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER;
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t output_offset = 0;
|
||||
if (!DecodePacket(static_cast<const uint8_t*>(input), static_cast<int>(input_size), output,
|
||||
output_size, &output_offset, gapless, &result)) {
|
||||
result.input_consumed = input_size;
|
||||
result.output_written = output_offset;
|
||||
result.total_decoded_samples = m_total_decoded_samples;
|
||||
result.format = GetFormat();
|
||||
return result;
|
||||
}
|
||||
|
||||
if (result.frames == 0 && result.result == OK) {
|
||||
result.result = AJM_RESULT_PARTIAL_INPUT;
|
||||
}
|
||||
|
||||
result.input_consumed = input_size;
|
||||
result.output_written = output_offset;
|
||||
result.total_decoded_samples = m_total_decoded_samples;
|
||||
result.format = GetFormat();
|
||||
return result;
|
||||
}
|
||||
|
||||
void WriteCodecInfo(void* output, size_t output_size,
|
||||
const AjmDecodeResult& result) const override {
|
||||
(void)result;
|
||||
if (output == nullptr || output_size < sizeof(AjmSidebandDecM4aacCodecInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* info = static_cast<AjmSidebandDecM4aacCodecInfo*>(output);
|
||||
info->heaac = ((m_flags & AJM_INSTANCE_FLAG_DEC_M4AAC_ENABLE_SBR_DECODE) != 0 ? 1u : 0u);
|
||||
info->reserved = 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t CodecInfoSize() const override {
|
||||
return sizeof(AjmSidebandDecM4aacCodecInfo);
|
||||
}
|
||||
|
||||
[[nodiscard]] AjmSidebandFormat GetFormat() const override {
|
||||
auto format = AjmMakeFormat(m_channels, m_sample_rate, m_sample_encoding);
|
||||
format.bitrate = m_bitrate;
|
||||
return format;
|
||||
}
|
||||
|
||||
private:
|
||||
static uint32_t SamplingRateFromIndex(uint32_t index) {
|
||||
static constexpr uint32_t SAMPLE_RATES[] = {
|
||||
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000,
|
||||
};
|
||||
return (index < (sizeof(SAMPLE_RATES) / sizeof(SAMPLE_RATES[0])) ? SAMPLE_RATES[index]
|
||||
: 48000u);
|
||||
}
|
||||
|
||||
void Release() {
|
||||
if (m_codec_context != nullptr) {
|
||||
avcodec_free_context(&m_codec_context);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenDecoder() {
|
||||
if (m_codec == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_codec_context = avcodec_alloc_context3(m_codec);
|
||||
if (m_codec_context == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_config_number == AJM_DEC_M4AAC_CONFIG_NUMBER_RAW) {
|
||||
const auto sample_index =
|
||||
std::min(m_sampling_freq_index, AJM_DEC_M4AAC_MAX_SAMPLING_FREQ_INDEX);
|
||||
const auto channels = std::clamp<uint32_t>(m_channels, 1u, AJM_DEC_M4AAC_MAX_CHANNELS);
|
||||
constexpr uint32_t audio_object_type = 2;
|
||||
|
||||
auto* extradata = static_cast<uint8_t*>(av_mallocz(2 + AV_INPUT_BUFFER_PADDING_SIZE));
|
||||
if (extradata == nullptr) {
|
||||
Release();
|
||||
return;
|
||||
}
|
||||
|
||||
extradata[0] = static_cast<uint8_t>((audio_object_type << 3u) | (sample_index >> 1u));
|
||||
extradata[1] = static_cast<uint8_t>(((sample_index & 0x1u) << 7u) |
|
||||
(std::min(channels, 7u) << 3u));
|
||||
m_codec_context->extradata = extradata;
|
||||
m_codec_context->extradata_size = 2;
|
||||
SetFormat(channels, SamplingRateFromIndex(sample_index), m_sample_encoding);
|
||||
}
|
||||
|
||||
if (avcodec_open2(m_codec_context, m_codec, nullptr) < 0) {
|
||||
LOGF("AJM AAC: decoder initialization failed\n");
|
||||
Release();
|
||||
}
|
||||
}
|
||||
|
||||
bool WriteFrame(const AVFrame* frame, void* output, size_t output_size, size_t* output_offset,
|
||||
AjmGaplessState* gapless, AjmDecodeResult* result) {
|
||||
if (frame == nullptr || output_offset == nullptr || result == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto channels = static_cast<uint32_t>(std::max(frame->ch_layout.nb_channels, 1));
|
||||
const auto bpf = channels * AjmBytesPerSample(m_sample_encoding);
|
||||
if (bpf == 0) {
|
||||
result->result = AJM_RESULT_INVALID_PARAMETER;
|
||||
return false;
|
||||
}
|
||||
|
||||
SetFormat(channels, static_cast<uint32_t>(std::max(frame->sample_rate, 1)),
|
||||
m_sample_encoding);
|
||||
if (m_codec_context != nullptr && m_codec_context->bit_rate > 0) {
|
||||
m_bitrate = static_cast<uint32_t>(m_codec_context->bit_rate);
|
||||
}
|
||||
|
||||
result->frames++;
|
||||
if (m_skip_frames > 0) {
|
||||
m_skip_frames--;
|
||||
result->format = GetFormat();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t skip_samples = 0;
|
||||
if (gapless != nullptr && gapless->current.skip_samples > 0) {
|
||||
skip_samples = std::min<uint32_t>(frame->nb_samples, gapless->current.skip_samples);
|
||||
gapless->current.skip_samples =
|
||||
static_cast<uint16_t>(gapless->current.skip_samples - skip_samples);
|
||||
}
|
||||
|
||||
uint32_t samples_to_write = static_cast<uint32_t>(frame->nb_samples) - skip_samples;
|
||||
if (gapless != nullptr && gapless->HasSampleLimit()) {
|
||||
samples_to_write = std::min(samples_to_write, gapless->current.total_samples);
|
||||
}
|
||||
|
||||
const auto requested_bytes = static_cast<size_t>(samples_to_write) * bpf;
|
||||
auto writable_bytes = requested_bytes;
|
||||
if (output == nullptr || *output_offset >= output_size) {
|
||||
writable_bytes = 0;
|
||||
} else {
|
||||
writable_bytes = std::min(writable_bytes, output_size - *output_offset);
|
||||
writable_bytes -= writable_bytes % bpf;
|
||||
}
|
||||
|
||||
if (writable_bytes != 0) {
|
||||
auto* dst = static_cast<uint8_t*>(output) + *output_offset;
|
||||
const auto* src = frame->data[0] + static_cast<size_t>(skip_samples) * bpf;
|
||||
std::memcpy(dst, src, writable_bytes);
|
||||
*output_offset += writable_bytes;
|
||||
}
|
||||
|
||||
const auto samples_written = static_cast<uint32_t>(writable_bytes / bpf);
|
||||
if (gapless != nullptr && gapless->HasSampleLimit()) {
|
||||
gapless->current.total_samples -=
|
||||
std::min(gapless->current.total_samples, samples_written);
|
||||
}
|
||||
if (gapless != nullptr) {
|
||||
const auto skipped_total =
|
||||
std::min<uint32_t>(std::numeric_limits<uint16_t>::max(),
|
||||
static_cast<uint32_t>(gapless->current.skipped_samples) +
|
||||
skip_samples + (samples_to_write - samples_written));
|
||||
gapless->current.skipped_samples = static_cast<uint16_t>(skipped_total);
|
||||
}
|
||||
|
||||
m_total_decoded_samples += samples_written;
|
||||
result->total_decoded_samples = m_total_decoded_samples;
|
||||
result->format = GetFormat();
|
||||
|
||||
if (writable_bytes != requested_bytes) {
|
||||
result->result |= AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodePacket(const uint8_t* packet_data, int packet_size, void* output, size_t output_size,
|
||||
size_t* output_offset, AjmGaplessState* gapless, AjmDecodeResult* result) {
|
||||
if (packet_data == nullptr || packet_size <= 0 || result == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
AVPacket* packet = av_packet_alloc();
|
||||
if (packet == nullptr || av_new_packet(packet, packet_size) < 0) {
|
||||
av_packet_free(&packet);
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return false;
|
||||
}
|
||||
std::memcpy(packet->data, packet_data, static_cast<size_t>(packet_size));
|
||||
|
||||
int rc = avcodec_send_packet(m_codec_context, packet);
|
||||
av_packet_free(&packet);
|
||||
if (rc == AVERROR_INVALIDDATA) {
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
if (rc < 0) {
|
||||
result->result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
if (frame == nullptr) {
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = avcodec_receive_frame(m_codec_context, frame);
|
||||
if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF) {
|
||||
av_frame_free(&frame);
|
||||
return true;
|
||||
}
|
||||
if (rc < 0) {
|
||||
av_frame_free(&frame);
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
|
||||
AVFrame* converted = AjmConvertFfmpegFrame(frame, m_sample_encoding, result);
|
||||
av_frame_free(&frame);
|
||||
if (converted == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool ok =
|
||||
WriteFrame(converted, output, output_size, output_offset, gapless, result);
|
||||
av_frame_free(&converted);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const AVCodec* m_codec = nullptr;
|
||||
AVCodecContext* m_codec_context = nullptr;
|
||||
uint64_t m_flags = 0;
|
||||
uint32_t m_config_number = AJM_DEC_M4AAC_CONFIG_NUMBER_ADTS;
|
||||
uint32_t m_sampling_freq_index = 3;
|
||||
uint32_t m_skip_frames = 2;
|
||||
uint32_t m_bitrate = 0;
|
||||
bool m_is_initialized = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Audio::Ajm
|
||||
@@ -0,0 +1,530 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
#include "libatrac9.h"
|
||||
}
|
||||
|
||||
namespace Libs::Audio::Ajm {
|
||||
|
||||
struct AjmDecAt9InitializeParameters {
|
||||
uint8_t config_data[ATRAC9_CONFIG_DATA_SIZE];
|
||||
uint32_t reserved;
|
||||
};
|
||||
|
||||
struct AjmSidebandDecAt9CodecInfo {
|
||||
uint32_t super_frame_size;
|
||||
uint32_t frames_in_super_frame;
|
||||
uint32_t next_frame_size;
|
||||
uint32_t frame_samples;
|
||||
};
|
||||
|
||||
static constexpr uint64_t AJM_INSTANCE_FLAG_DEC_AT9_PARSE_RIFF_HEADER =
|
||||
1ull << (AJM_INSTANCE_FLAG_CODEC_OFFSET + 0u);
|
||||
|
||||
static uint16_t AjmReadLe16(const uint8_t* data) {
|
||||
return static_cast<uint16_t>(data[0]) | (static_cast<uint16_t>(data[1]) << 8u);
|
||||
}
|
||||
|
||||
static uint32_t AjmReadLe32(const uint8_t* data) {
|
||||
return static_cast<uint32_t>(data[0]) | (static_cast<uint32_t>(data[1]) << 8u) |
|
||||
(static_cast<uint32_t>(data[2]) << 16u) | (static_cast<uint32_t>(data[3]) << 24u);
|
||||
}
|
||||
|
||||
static bool AjmFourCcEquals(const uint8_t* data, char a, char b, char c, char d) {
|
||||
return data[0] == static_cast<uint8_t>(a) && data[1] == static_cast<uint8_t>(b) &&
|
||||
data[2] == static_cast<uint8_t>(c) && data[3] == static_cast<uint8_t>(d);
|
||||
}
|
||||
|
||||
class AjmAt9Decoder final: public AjmDecoder {
|
||||
public:
|
||||
AjmAt9Decoder(uint32_t channels, uint32_t sample_rate, AjmSampleEncoding encoding,
|
||||
uint64_t flags)
|
||||
: AjmDecoder(channels, sample_rate, encoding),
|
||||
m_parse_riff_header((flags & AJM_INSTANCE_FLAG_DEC_AT9_PARSE_RIFF_HEADER) != 0),
|
||||
m_handle(Atrac9GetHandle()) {}
|
||||
|
||||
~AjmAt9Decoder() override {
|
||||
if (m_handle != nullptr) {
|
||||
Atrac9ReleaseHandle(m_handle);
|
||||
}
|
||||
}
|
||||
|
||||
AjmDecodeResult Initialize(const void* codec_parameters,
|
||||
size_t codec_parameters_size) override {
|
||||
auto result = MakeResult();
|
||||
|
||||
if (codec_parameters == nullptr || codec_parameters_size < ATRAC9_CONFIG_DATA_SIZE) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER;
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto* params = static_cast<const AjmDecAt9InitializeParameters*>(codec_parameters);
|
||||
if (!InitializeConfig(params->config_data, &result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return MakeResult();
|
||||
}
|
||||
|
||||
void Reset() override {
|
||||
if (m_handle != nullptr) {
|
||||
Atrac9ReleaseHandle(m_handle);
|
||||
}
|
||||
m_handle = Atrac9GetHandle();
|
||||
m_num_frames = 0;
|
||||
m_total_decoded_samples = 0;
|
||||
m_superframe_bytes_remain =
|
||||
(m_is_initialized ? static_cast<uint32_t>(m_codec_info.superframeSize) : 0);
|
||||
|
||||
if (m_has_config && m_handle != nullptr) {
|
||||
auto result = MakeResult();
|
||||
(void)InitializeConfig(m_config_data, &result);
|
||||
}
|
||||
}
|
||||
|
||||
AjmDecodeResult Decode(const void* input, size_t input_size, void* output, size_t output_size,
|
||||
bool multiple_frames, AjmGaplessState* gapless) override {
|
||||
auto result = MakeResult();
|
||||
|
||||
if (input == nullptr || input_size == 0) {
|
||||
result.result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto* input_bytes = static_cast<const uint8_t*>(input);
|
||||
auto* output_bytes = static_cast<uint8_t*>(output);
|
||||
size_t input_offset = 0;
|
||||
|
||||
if (m_parse_riff_header && input_size >= 12 &&
|
||||
AjmFourCcEquals(input_bytes, 'R', 'I', 'F', 'F')) {
|
||||
if (!ParseRiffHeader(input_bytes, input_size, &input_offset, gapless, &result)) {
|
||||
return result;
|
||||
}
|
||||
if (input_offset >= input_size) {
|
||||
result.input_consumed = input_offset;
|
||||
result.result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_is_initialized) {
|
||||
result.result = AJM_RESULT_NOT_INITIALIZED;
|
||||
return result;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (input_offset >= input_size) {
|
||||
break;
|
||||
}
|
||||
if (gapless != nullptr && gapless->IsEnd()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto remaining_input = input_size - input_offset;
|
||||
if (remaining_input < GetMinimumInputSize()) {
|
||||
if (result.frames == 0) {
|
||||
result.result = AJM_RESULT_PARTIAL_INPUT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const auto frame_output_bytes = FrameOutputBytes(gapless);
|
||||
if (frame_output_bytes != 0 && output == nullptr) {
|
||||
result.result |= AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
break;
|
||||
}
|
||||
if (frame_output_bytes == 0 && !FrameCanProduceNoOutput(gapless)) {
|
||||
result.result |= AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
break;
|
||||
}
|
||||
if (output_size - result.output_written < frame_output_bytes) {
|
||||
result.result |= AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
break;
|
||||
}
|
||||
|
||||
int bytes_used = 0;
|
||||
const int ret = DecodeFrame(input_bytes + input_offset, &bytes_used);
|
||||
if (ret != 0) {
|
||||
result.result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
result.internal_result = ret;
|
||||
break;
|
||||
}
|
||||
if (bytes_used <= 0 || static_cast<size_t>(bytes_used) > remaining_input ||
|
||||
static_cast<uint32_t>(bytes_used) > m_superframe_bytes_remain) {
|
||||
result.result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto samples_written =
|
||||
CopyDecodedFrame(output_bytes, output_size, &result.output_written, gapless);
|
||||
|
||||
input_offset += static_cast<size_t>(bytes_used);
|
||||
result.input_consumed = input_offset;
|
||||
result.frames++;
|
||||
m_total_decoded_samples += samples_written;
|
||||
|
||||
AdvanceSuperframe(static_cast<uint32_t>(bytes_used), input_size, &input_offset,
|
||||
&result);
|
||||
if (gapless != nullptr && gapless->IsEnd()) {
|
||||
DrainSuperframe(input_bytes, input_size, &input_offset, &result);
|
||||
}
|
||||
if (result.result != OK || !multiple_frames) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.total_decoded_samples = m_total_decoded_samples;
|
||||
result.format = GetFormat();
|
||||
return result;
|
||||
}
|
||||
|
||||
void WriteCodecInfo(void* output, size_t output_size,
|
||||
const AjmDecodeResult& result) const override {
|
||||
(void)result;
|
||||
if (output == nullptr || output_size < sizeof(AjmSidebandDecAt9CodecInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* info = static_cast<AjmSidebandDecAt9CodecInfo*>(output);
|
||||
info->super_frame_size =
|
||||
m_is_initialized ? static_cast<uint32_t>(m_codec_info.superframeSize) : 0;
|
||||
info->frames_in_super_frame =
|
||||
m_is_initialized ? static_cast<uint32_t>(m_codec_info.framesInSuperframe) : 0;
|
||||
info->next_frame_size = GetMinimumInputSize();
|
||||
info->frame_samples =
|
||||
m_is_initialized ? static_cast<uint32_t>(m_codec_info.frameSamples) : 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t CodecInfoSize() const override {
|
||||
return sizeof(AjmSidebandDecAt9CodecInfo);
|
||||
}
|
||||
|
||||
[[nodiscard]] AjmSidebandFormat GetFormat() const override {
|
||||
auto format = AjmMakeFormat(
|
||||
m_is_initialized ? static_cast<uint32_t>(m_codec_info.channels) : m_channels,
|
||||
m_is_initialized ? static_cast<uint32_t>(m_codec_info.samplingRate) : m_sample_rate,
|
||||
m_sample_encoding);
|
||||
if (m_is_initialized && m_codec_info.framesInSuperframe > 0 &&
|
||||
m_codec_info.frameSamples > 0) {
|
||||
format.bitrate =
|
||||
static_cast<uint32_t>((static_cast<uint64_t>(m_codec_info.samplingRate) *
|
||||
static_cast<uint64_t>(m_codec_info.superframeSize) * 8u) /
|
||||
(static_cast<uint64_t>(m_codec_info.framesInSuperframe) *
|
||||
static_cast<uint64_t>(m_codec_info.frameSamples)));
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] uint32_t FrameOutputSamples(const AjmGaplessState* gapless) const {
|
||||
if (!m_is_initialized || m_codec_info.channels <= 0 || m_codec_info.frameSamples <= 0) {
|
||||
return 0;
|
||||
}
|
||||
auto samples = static_cast<uint32_t>(m_codec_info.frameSamples);
|
||||
if (gapless != nullptr) {
|
||||
const auto skip_samples = std::min<uint32_t>(samples, gapless->current.skip_samples);
|
||||
samples -= skip_samples;
|
||||
if (gapless->HasSampleLimit()) {
|
||||
samples = std::min<uint32_t>(samples, gapless->current.total_samples);
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool FrameCanProduceNoOutput(const AjmGaplessState* gapless) const {
|
||||
return m_is_initialized && m_codec_info.channels > 0 && m_codec_info.frameSamples > 0 &&
|
||||
gapless != nullptr &&
|
||||
(gapless->current.skip_samples != 0 ||
|
||||
(gapless->HasSampleLimit() && gapless->current.total_samples == 0));
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t FrameOutputBytes(const AjmGaplessState* gapless = nullptr) const {
|
||||
if (!m_is_initialized || m_codec_info.channels <= 0 || m_codec_info.frameSamples <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<size_t>(m_codec_info.channels) *
|
||||
static_cast<size_t>(FrameOutputSamples(gapless)) *
|
||||
AjmBytesPerSample(m_sample_encoding);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t GetMinimumInputSize() const {
|
||||
return (m_is_initialized ? m_superframe_bytes_remain : 0);
|
||||
}
|
||||
|
||||
bool InitializeConfig(const uint8_t* config_data, AjmDecodeResult* result) {
|
||||
if (config_data == nullptr || result == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (m_handle == nullptr) {
|
||||
m_handle = Atrac9GetHandle();
|
||||
}
|
||||
if (m_handle == nullptr) {
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(m_config_data, config_data, ATRAC9_CONFIG_DATA_SIZE);
|
||||
m_has_config = true;
|
||||
|
||||
const int init_ret = Atrac9InitDecoder(m_handle, m_config_data);
|
||||
if (init_ret != 0) {
|
||||
m_is_initialized = false;
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
result->internal_result = init_ret;
|
||||
return false;
|
||||
}
|
||||
|
||||
const int info_ret = Atrac9GetCodecInfo(m_handle, &m_codec_info);
|
||||
if (info_ret != 0 || m_codec_info.channels <= 0 || m_codec_info.frameSamples <= 0 ||
|
||||
m_codec_info.superframeSize <= 0 || m_codec_info.framesInSuperframe <= 0) {
|
||||
m_is_initialized = false;
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
result->internal_result = info_ret;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_is_initialized = true;
|
||||
m_num_frames = 0;
|
||||
m_total_decoded_samples = 0;
|
||||
m_superframe_bytes_remain = static_cast<uint32_t>(m_codec_info.superframeSize);
|
||||
SetFormat(static_cast<uint32_t>(m_codec_info.channels),
|
||||
static_cast<uint32_t>(m_codec_info.samplingRate), m_sample_encoding);
|
||||
m_pcm_buffer.assign(FrameOutputBytes(), 0);
|
||||
|
||||
LOGF("AJM ATRAC9 initialized: %d Hz, %d ch, frame_samples=%d, superframe=%d bytes/%d "
|
||||
"frames\n",
|
||||
m_codec_info.samplingRate, m_codec_info.channels, m_codec_info.frameSamples,
|
||||
m_codec_info.superframeSize, m_codec_info.framesInSuperframe);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int DecodeFrame(const uint8_t* input, int* bytes_used) {
|
||||
switch (m_sample_encoding) {
|
||||
case AjmSampleEncoding::S16:
|
||||
return Atrac9Decode(m_handle, input,
|
||||
reinterpret_cast<int16_t*>(m_pcm_buffer.data()), bytes_used, 0);
|
||||
case AjmSampleEncoding::S32:
|
||||
return Atrac9DecodeS32(m_handle, input,
|
||||
reinterpret_cast<int32_t*>(m_pcm_buffer.data()), bytes_used,
|
||||
0);
|
||||
case AjmSampleEncoding::Float:
|
||||
return Atrac9DecodeF32(
|
||||
m_handle, input, reinterpret_cast<float*>(m_pcm_buffer.data()), bytes_used, 0);
|
||||
default:
|
||||
EXIT("unsupported AJM PCM sample encoding %u\n",
|
||||
static_cast<uint32_t>(m_sample_encoding));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t CopyDecodedFrame(uint8_t* output, size_t output_size, size_t* output_written,
|
||||
AjmGaplessState* gapless) {
|
||||
EXIT_IF(output_written == nullptr);
|
||||
if (!m_is_initialized || m_codec_info.channels <= 0 || m_codec_info.frameSamples <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto channels = static_cast<uint32_t>(m_codec_info.channels);
|
||||
const auto frame_samples = static_cast<uint32_t>(m_codec_info.frameSamples);
|
||||
const auto bytes_per_frame =
|
||||
static_cast<size_t>(channels) * AjmBytesPerSample(m_sample_encoding);
|
||||
auto skip_samples = 0u;
|
||||
auto samples_to_write = frame_samples;
|
||||
|
||||
if (gapless != nullptr) {
|
||||
skip_samples = std::min<uint32_t>(frame_samples, gapless->current.skip_samples);
|
||||
gapless->current.skip_samples =
|
||||
static_cast<uint16_t>(gapless->current.skip_samples - skip_samples);
|
||||
|
||||
samples_to_write -= skip_samples;
|
||||
if (gapless->HasSampleLimit()) {
|
||||
samples_to_write =
|
||||
std::min<uint32_t>(samples_to_write, gapless->current.total_samples);
|
||||
}
|
||||
}
|
||||
|
||||
const auto copy_bytes = static_cast<size_t>(samples_to_write) * bytes_per_frame;
|
||||
if (copy_bytes != 0) {
|
||||
EXIT_IF(output == nullptr);
|
||||
EXIT_IF(*output_written > output_size || copy_bytes > output_size - *output_written);
|
||||
std::memcpy(output + *output_written,
|
||||
m_pcm_buffer.data() + static_cast<size_t>(skip_samples) * bytes_per_frame,
|
||||
copy_bytes);
|
||||
*output_written += copy_bytes;
|
||||
}
|
||||
|
||||
if (gapless != nullptr) {
|
||||
if (gapless->HasSampleLimit()) {
|
||||
gapless->current.total_samples =
|
||||
(gapless->current.total_samples > samples_to_write
|
||||
? gapless->current.total_samples - samples_to_write
|
||||
: 0);
|
||||
}
|
||||
|
||||
const auto skipped_samples = frame_samples - samples_to_write;
|
||||
const auto skipped_total = std::min<uint32_t>(
|
||||
std::numeric_limits<uint16_t>::max(),
|
||||
static_cast<uint32_t>(gapless->current.skipped_samples) + skipped_samples);
|
||||
gapless->current.skipped_samples = static_cast<uint16_t>(skipped_total);
|
||||
}
|
||||
|
||||
return samples_to_write;
|
||||
}
|
||||
|
||||
void AdvanceSuperframe(uint32_t bytes_used, size_t input_size, size_t* input_offset,
|
||||
AjmDecodeResult* result) {
|
||||
EXIT_IF(input_offset == nullptr);
|
||||
EXIT_IF(result == nullptr);
|
||||
|
||||
m_superframe_bytes_remain -= bytes_used;
|
||||
m_num_frames++;
|
||||
|
||||
if (m_num_frames < static_cast<uint32_t>(m_codec_info.framesInSuperframe)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_superframe_bytes_remain != 0) {
|
||||
const auto remaining_input =
|
||||
(*input_offset < input_size ? input_size - *input_offset : 0);
|
||||
if (remaining_input < m_superframe_bytes_remain) {
|
||||
result->result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return;
|
||||
}
|
||||
*input_offset += m_superframe_bytes_remain;
|
||||
result->input_consumed = *input_offset;
|
||||
}
|
||||
|
||||
m_superframe_bytes_remain = static_cast<uint32_t>(m_codec_info.superframeSize);
|
||||
m_num_frames = 0;
|
||||
}
|
||||
|
||||
void DrainSuperframe(const uint8_t* input, size_t input_size, size_t* input_offset,
|
||||
AjmDecodeResult* result) {
|
||||
EXIT_IF(input_offset == nullptr);
|
||||
EXIT_IF(result == nullptr);
|
||||
|
||||
while (m_num_frames != 0) {
|
||||
const auto remaining_input =
|
||||
(*input_offset < input_size ? input_size - *input_offset : 0);
|
||||
if (remaining_input < GetMinimumInputSize()) {
|
||||
result->result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return;
|
||||
}
|
||||
|
||||
int bytes_used = 0;
|
||||
const int ret = DecodeFrame(input + *input_offset, &bytes_used);
|
||||
if (ret != 0) {
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
result->internal_result = ret;
|
||||
return;
|
||||
}
|
||||
if (bytes_used <= 0 || static_cast<size_t>(bytes_used) > remaining_input ||
|
||||
static_cast<uint32_t>(bytes_used) > m_superframe_bytes_remain) {
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
return;
|
||||
}
|
||||
|
||||
*input_offset += static_cast<size_t>(bytes_used);
|
||||
result->input_consumed = *input_offset;
|
||||
result->frames++;
|
||||
AdvanceSuperframe(static_cast<uint32_t>(bytes_used), input_size, input_offset, result);
|
||||
if (result->result != OK) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ParseRiffHeader(const uint8_t* input, size_t input_size, size_t* data_offset,
|
||||
AjmGaplessState* gapless, AjmDecodeResult* result) {
|
||||
EXIT_IF(data_offset == nullptr);
|
||||
EXIT_IF(result == nullptr);
|
||||
|
||||
*data_offset = 0;
|
||||
if (input_size < 12 || !AjmFourCcEquals(input, 'R', 'I', 'F', 'F') ||
|
||||
!AjmFourCcEquals(input + 8, 'W', 'A', 'V', 'E')) {
|
||||
result->result = AJM_RESULT_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t offset = 12;
|
||||
while (offset + 8 <= input_size) {
|
||||
const auto* chunk = input + offset;
|
||||
const auto chunk_size = static_cast<size_t>(AjmReadLe32(chunk + 4));
|
||||
const auto payload = offset + 8;
|
||||
if (payload > input_size || chunk_size > input_size - payload) {
|
||||
result->result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AjmFourCcEquals(chunk, 'f', 'm', 't', ' ')) {
|
||||
if (chunk_size < 48) {
|
||||
result->result = AJM_RESULT_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
const auto* fmt = input + payload;
|
||||
static constexpr uint8_t AT9_GUID[16] = {0xd2, 0x42, 0xe1, 0x47, 0xba, 0x36,
|
||||
0x8d, 0x4d, 0x88, 0xfc, 0x61, 0x65,
|
||||
0x4f, 0x8c, 0x83, 0x6c};
|
||||
if (AjmReadLe16(fmt) != 0xfffe ||
|
||||
std::memcmp(fmt + 24, AT9_GUID, sizeof(AT9_GUID)) != 0) {
|
||||
result->result = AJM_RESULT_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
if (!InitializeConfig(fmt + 44, result)) {
|
||||
return false;
|
||||
}
|
||||
} else if (AjmFourCcEquals(chunk, 'f', 'a', 'c', 't')) {
|
||||
if (chunk_size >= 12 && gapless != nullptr) {
|
||||
AjmSidebandGaplessDecode gapless_decode {};
|
||||
gapless_decode.total_samples = AjmReadLe32(input + payload);
|
||||
gapless_decode.skip_samples = static_cast<uint16_t>(std::min<uint32_t>(
|
||||
AjmReadLe32(input + payload + 4), std::numeric_limits<uint16_t>::max()));
|
||||
gapless->Set(gapless_decode, true);
|
||||
LOGF("AJM ATRAC9 gapless: total=%" PRIu32 ", skip=%" PRIu16 "\n",
|
||||
gapless_decode.total_samples, gapless_decode.skip_samples);
|
||||
}
|
||||
} else if (AjmFourCcEquals(chunk, 'd', 'a', 't', 'a')) {
|
||||
*data_offset = payload;
|
||||
result->input_consumed = payload;
|
||||
result->format = GetFormat();
|
||||
result->total_decoded_samples = m_total_decoded_samples;
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto padded_size = chunk_size + (chunk_size & 1u);
|
||||
if (padded_size > std::numeric_limits<size_t>::max() - 8 ||
|
||||
offset > std::numeric_limits<size_t>::max() - 8 - padded_size) {
|
||||
result->result = AJM_RESULT_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
offset += 8 + padded_size;
|
||||
}
|
||||
|
||||
result->result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool m_parse_riff_header = false;
|
||||
bool m_is_initialized = false;
|
||||
bool m_has_config = false;
|
||||
void* m_handle = nullptr;
|
||||
uint8_t m_config_data[ATRAC9_CONFIG_DATA_SIZE] {};
|
||||
uint32_t m_superframe_bytes_remain = 0;
|
||||
uint32_t m_num_frames = 0;
|
||||
Atrac9CodecInfo m_codec_info {};
|
||||
std::vector<uint8_t> m_pcm_buffer;
|
||||
};
|
||||
|
||||
} // namespace Libs::Audio::Ajm
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/mem.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
namespace Libs::Audio::Ajm {
|
||||
|
||||
static AVSampleFormat AjmSampleEncodingToAvFormat(AjmSampleEncoding encoding) {
|
||||
switch (encoding) {
|
||||
case AjmSampleEncoding::S16: return AV_SAMPLE_FMT_S16;
|
||||
case AjmSampleEncoding::S32: return AV_SAMPLE_FMT_S32;
|
||||
case AjmSampleEncoding::Float: return AV_SAMPLE_FMT_FLT;
|
||||
default: EXIT("unsupported AJM PCM sample encoding %u\n", static_cast<uint32_t>(encoding));
|
||||
}
|
||||
return AV_SAMPLE_FMT_S16;
|
||||
}
|
||||
|
||||
static AVFrame* AjmConvertFfmpegFrame(const AVFrame* frame, AjmSampleEncoding encoding,
|
||||
AjmDecodeResult* result) {
|
||||
if (frame == nullptr || result == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AVFrame* converted = av_frame_alloc();
|
||||
if (converted == nullptr) {
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
converted->format = AjmSampleEncodingToAvFormat(encoding);
|
||||
converted->sample_rate = frame->sample_rate;
|
||||
converted->nb_samples = frame->nb_samples;
|
||||
if (av_channel_layout_copy(&converted->ch_layout, &frame->ch_layout) < 0 ||
|
||||
av_frame_get_buffer(converted, 0) < 0) {
|
||||
av_frame_free(&converted);
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SwrContext* swr = nullptr;
|
||||
int swr_result = swr_alloc_set_opts2(
|
||||
&swr, &converted->ch_layout, static_cast<AVSampleFormat>(converted->format),
|
||||
converted->sample_rate, &frame->ch_layout, static_cast<AVSampleFormat>(frame->format),
|
||||
frame->sample_rate, 0, nullptr);
|
||||
if (swr_result < 0 || swr_init(swr) < 0 || swr_convert_frame(swr, converted, frame) < 0) {
|
||||
swr_free(&swr);
|
||||
av_frame_free(&converted);
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
return nullptr;
|
||||
}
|
||||
swr_free(&swr);
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
} // namespace Libs::Audio::Ajm
|
||||
@@ -0,0 +1,393 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "libs/ajm/ffmpeg_decoder_common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace Libs::Audio::Ajm {
|
||||
|
||||
struct AjmSidebandDecMp3CodecInfo {
|
||||
uint32_t header;
|
||||
uint8_t crc;
|
||||
uint8_t mode;
|
||||
uint8_t mode_extension;
|
||||
uint8_t copyright;
|
||||
uint8_t original;
|
||||
uint8_t emphasis;
|
||||
uint16_t reserved[3];
|
||||
};
|
||||
|
||||
static_assert(sizeof(AjmSidebandDecMp3CodecInfo) == 16);
|
||||
|
||||
struct AjmMp3FrameHeader {
|
||||
uint32_t header = 0;
|
||||
uint32_t frame_size = 0;
|
||||
uint32_t num_channels = 0;
|
||||
uint32_t samples_per_channel = 0;
|
||||
uint32_t bitrate = 0;
|
||||
uint32_t sample_rate = 0;
|
||||
uint8_t crc = 0;
|
||||
uint8_t mode = 0;
|
||||
uint8_t mode_extension = 0;
|
||||
uint8_t copyright = 0;
|
||||
uint8_t original = 0;
|
||||
uint8_t emphasis = 0;
|
||||
};
|
||||
|
||||
static uint32_t AjmReadBe32(const uint8_t* data) {
|
||||
return (static_cast<uint32_t>(data[0]) << 24u) | (static_cast<uint32_t>(data[1]) << 16u) |
|
||||
(static_cast<uint32_t>(data[2]) << 8u) | static_cast<uint32_t>(data[3]);
|
||||
}
|
||||
|
||||
static bool AjmParseMp3Header(const uint8_t* data, size_t size, AjmMp3FrameHeader* out) {
|
||||
if (data == nullptr || size < 4 || out == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t header = AjmReadBe32(data);
|
||||
if ((header & 0xffe00000u) != 0xffe00000u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto version_id = (header >> 19u) & 0x3u;
|
||||
const auto layer_id = (header >> 17u) & 0x3u;
|
||||
const auto protection_bit = (header >> 16u) & 0x1u;
|
||||
const auto bitrate_index = (header >> 12u) & 0xfu;
|
||||
const auto sample_index = (header >> 10u) & 0x3u;
|
||||
const auto padding = (header >> 9u) & 0x1u;
|
||||
const auto channel_mode = (header >> 6u) & 0x3u;
|
||||
const auto mode_extension = (header >> 4u) & 0x3u;
|
||||
const auto copyright = (header >> 3u) & 0x1u;
|
||||
const auto original = (header >> 2u) & 0x1u;
|
||||
const auto emphasis = header & 0x3u;
|
||||
|
||||
if (version_id == 1 || layer_id != 1 || sample_index == 3 || bitrate_index == 0 ||
|
||||
bitrate_index == 15) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr uint32_t SAMPLE_RATES[4][3] = {
|
||||
{11025, 12000, 8000},
|
||||
{0, 0, 0},
|
||||
{22050, 24000, 16000},
|
||||
{44100, 48000, 32000},
|
||||
};
|
||||
static constexpr uint32_t BITRATES_MPEG1[16] = {
|
||||
0, 32000, 40000, 48000, 56000, 64000, 80000, 96000,
|
||||
112000, 128000, 160000, 192000, 224000, 256000, 320000, 0,
|
||||
};
|
||||
static constexpr uint32_t BITRATES_MPEG2[16] = {
|
||||
0, 8000, 16000, 24000, 32000, 40000, 48000, 56000,
|
||||
64000, 80000, 96000, 112000, 128000, 144000, 160000, 0,
|
||||
};
|
||||
|
||||
const auto sample_rate = SAMPLE_RATES[version_id][sample_index];
|
||||
const auto bitrate =
|
||||
(version_id == 3 ? BITRATES_MPEG1[bitrate_index] : BITRATES_MPEG2[bitrate_index]);
|
||||
if (sample_rate == 0 || bitrate == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool mpeg1 = (version_id == 3);
|
||||
|
||||
out->header = header;
|
||||
out->frame_size =
|
||||
(mpeg1 ? (144u * bitrate) / sample_rate : (72u * bitrate) / sample_rate) + padding;
|
||||
out->num_channels = (channel_mode == 3 ? 1u : 2u);
|
||||
out->samples_per_channel = (mpeg1 ? 1152u : 576u);
|
||||
out->bitrate = bitrate;
|
||||
out->sample_rate = sample_rate;
|
||||
out->crc = static_cast<uint8_t>(protection_bit);
|
||||
out->mode = static_cast<uint8_t>(channel_mode);
|
||||
out->mode_extension = static_cast<uint8_t>(mode_extension);
|
||||
out->copyright = static_cast<uint8_t>(copyright);
|
||||
out->original = static_cast<uint8_t>(original);
|
||||
out->emphasis = static_cast<uint8_t>(emphasis);
|
||||
return true;
|
||||
}
|
||||
|
||||
class AjmMp3Decoder final: public AjmDecoder {
|
||||
public:
|
||||
AjmMp3Decoder(uint32_t channels, uint32_t sample_rate, AjmSampleEncoding encoding,
|
||||
uint64_t flags)
|
||||
: AjmDecoder(channels, sample_rate, encoding) {
|
||||
(void)flags;
|
||||
|
||||
m_codec = avcodec_find_decoder(AV_CODEC_ID_MP3);
|
||||
if (m_codec == nullptr) {
|
||||
LOGF("AJM MP3: avcodec_find_decoder failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
m_codec_context = avcodec_alloc_context3(m_codec);
|
||||
m_parser = av_parser_init(m_codec->id);
|
||||
if (m_codec_context == nullptr || m_parser == nullptr ||
|
||||
avcodec_open2(m_codec_context, m_codec, nullptr) < 0) {
|
||||
LOGF("AJM MP3: decoder initialization failed\n");
|
||||
Release();
|
||||
}
|
||||
}
|
||||
|
||||
~AjmMp3Decoder() override { Release(); }
|
||||
|
||||
void Reset() override {
|
||||
if (m_codec_context != nullptr) {
|
||||
avcodec_flush_buffers(m_codec_context);
|
||||
}
|
||||
if (m_parser != nullptr) {
|
||||
av_parser_close(m_parser);
|
||||
m_parser = (m_codec != nullptr ? av_parser_init(m_codec->id) : nullptr);
|
||||
}
|
||||
m_total_decoded_samples = 0;
|
||||
m_have_header = false;
|
||||
m_bitrate = 0;
|
||||
}
|
||||
|
||||
AjmDecodeResult Decode(const void* input, size_t input_size, void* output, size_t output_size,
|
||||
bool multiple_frames, AjmGaplessState* gapless) override {
|
||||
auto result = MakeResult();
|
||||
|
||||
if (m_codec_context == nullptr || m_parser == nullptr) {
|
||||
result.result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return result;
|
||||
}
|
||||
if (input == nullptr || input_size == 0) {
|
||||
result.result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return result;
|
||||
}
|
||||
if (input_size > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER;
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto* input_bytes = static_cast<const uint8_t*>(input);
|
||||
size_t input_offset = 0;
|
||||
size_t output_offset = 0;
|
||||
bool decoded = false;
|
||||
|
||||
while (input_offset < input_size) {
|
||||
AjmMp3FrameHeader header {};
|
||||
if (AjmParseMp3Header(input_bytes + input_offset, input_size - input_offset, &header)) {
|
||||
m_last_header = header;
|
||||
m_have_header = true;
|
||||
}
|
||||
|
||||
uint8_t* packet_data = nullptr;
|
||||
int packet_size = 0;
|
||||
const int consumed = av_parser_parse2(
|
||||
m_parser, m_codec_context, &packet_data, &packet_size, input_bytes + input_offset,
|
||||
static_cast<int>(input_size - input_offset), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
|
||||
if (consumed < 0) {
|
||||
result.result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
break;
|
||||
}
|
||||
|
||||
input_offset += static_cast<size_t>(consumed);
|
||||
if (packet_size > 0) {
|
||||
if (!DecodePacket(packet_data, packet_size, output, output_size, &output_offset,
|
||||
gapless, &result)) {
|
||||
break;
|
||||
}
|
||||
decoded = true;
|
||||
if (!multiple_frames) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (consumed == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!decoded && result.result == OK) {
|
||||
result.result = AJM_RESULT_PARTIAL_INPUT;
|
||||
}
|
||||
|
||||
result.input_consumed = input_offset;
|
||||
result.output_written = output_offset;
|
||||
result.total_decoded_samples = m_total_decoded_samples;
|
||||
result.format = GetFormat();
|
||||
return result;
|
||||
}
|
||||
|
||||
void WriteCodecInfo(void* output, size_t output_size,
|
||||
const AjmDecodeResult& result) const override {
|
||||
(void)result;
|
||||
if (output == nullptr || output_size < sizeof(AjmSidebandDecMp3CodecInfo) ||
|
||||
!m_have_header) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* info = static_cast<AjmSidebandDecMp3CodecInfo*>(output);
|
||||
info->header = m_last_header.header;
|
||||
info->crc = m_last_header.crc;
|
||||
info->mode = m_last_header.mode;
|
||||
info->mode_extension = m_last_header.mode_extension;
|
||||
info->copyright = m_last_header.copyright;
|
||||
info->original = m_last_header.original;
|
||||
info->emphasis = m_last_header.emphasis;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t CodecInfoSize() const override {
|
||||
return sizeof(AjmSidebandDecMp3CodecInfo);
|
||||
}
|
||||
|
||||
[[nodiscard]] AjmSidebandFormat GetFormat() const override {
|
||||
auto format = AjmMakeFormat(m_channels, m_sample_rate, m_sample_encoding);
|
||||
format.bitrate = m_bitrate;
|
||||
return format;
|
||||
}
|
||||
|
||||
private:
|
||||
void Release() {
|
||||
if (m_parser != nullptr) {
|
||||
av_parser_close(m_parser);
|
||||
m_parser = nullptr;
|
||||
}
|
||||
if (m_codec_context != nullptr) {
|
||||
avcodec_free_context(&m_codec_context);
|
||||
}
|
||||
}
|
||||
|
||||
bool WriteFrame(const AVFrame* frame, void* output, size_t output_size, size_t* output_offset,
|
||||
AjmGaplessState* gapless, AjmDecodeResult* result) {
|
||||
if (frame == nullptr || output_offset == nullptr || result == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto channels = static_cast<uint32_t>(std::max(frame->ch_layout.nb_channels, 1));
|
||||
const auto bpf = channels * AjmBytesPerSample(m_sample_encoding);
|
||||
if (bpf == 0) {
|
||||
result->result = AJM_RESULT_INVALID_PARAMETER;
|
||||
return false;
|
||||
}
|
||||
|
||||
SetFormat(channels, static_cast<uint32_t>(std::max(frame->sample_rate, 1)),
|
||||
m_sample_encoding);
|
||||
if (m_codec_context != nullptr && m_codec_context->bit_rate > 0) {
|
||||
m_bitrate = static_cast<uint32_t>(m_codec_context->bit_rate);
|
||||
} else if (m_have_header) {
|
||||
m_bitrate = m_last_header.bitrate;
|
||||
}
|
||||
|
||||
uint32_t skip_samples = 0;
|
||||
if (gapless != nullptr && gapless->current.skip_samples > 0) {
|
||||
skip_samples = std::min<uint32_t>(frame->nb_samples, gapless->current.skip_samples);
|
||||
gapless->current.skip_samples =
|
||||
static_cast<uint16_t>(gapless->current.skip_samples - skip_samples);
|
||||
}
|
||||
|
||||
uint32_t samples_to_write = static_cast<uint32_t>(frame->nb_samples) - skip_samples;
|
||||
if (gapless != nullptr && gapless->HasSampleLimit()) {
|
||||
samples_to_write = std::min(samples_to_write, gapless->current.total_samples);
|
||||
}
|
||||
|
||||
const auto requested_bytes = static_cast<size_t>(samples_to_write) * bpf;
|
||||
auto writable_bytes = requested_bytes;
|
||||
if (output == nullptr || *output_offset >= output_size) {
|
||||
writable_bytes = 0;
|
||||
} else {
|
||||
writable_bytes = std::min(writable_bytes, output_size - *output_offset);
|
||||
writable_bytes -= writable_bytes % bpf;
|
||||
}
|
||||
|
||||
if (writable_bytes != 0) {
|
||||
auto* dst = static_cast<uint8_t*>(output) + *output_offset;
|
||||
const auto* src = frame->data[0] + static_cast<size_t>(skip_samples) * bpf;
|
||||
std::memcpy(dst, src, writable_bytes);
|
||||
*output_offset += writable_bytes;
|
||||
}
|
||||
|
||||
const auto samples_written = static_cast<uint32_t>(writable_bytes / bpf);
|
||||
if (gapless != nullptr && gapless->HasSampleLimit()) {
|
||||
gapless->current.total_samples -=
|
||||
std::min(gapless->current.total_samples, samples_written);
|
||||
}
|
||||
if (gapless != nullptr) {
|
||||
const auto skipped_total =
|
||||
std::min<uint32_t>(std::numeric_limits<uint16_t>::max(),
|
||||
static_cast<uint32_t>(gapless->current.skipped_samples) +
|
||||
skip_samples + (samples_to_write - samples_written));
|
||||
gapless->current.skipped_samples = static_cast<uint16_t>(skipped_total);
|
||||
}
|
||||
|
||||
m_total_decoded_samples += samples_written;
|
||||
result->frames++;
|
||||
result->total_decoded_samples = m_total_decoded_samples;
|
||||
result->format = GetFormat();
|
||||
|
||||
if (writable_bytes != requested_bytes) {
|
||||
result->result |= AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodePacket(const uint8_t* packet_data, int packet_size, void* output, size_t output_size,
|
||||
size_t* output_offset, AjmGaplessState* gapless, AjmDecodeResult* result) {
|
||||
if (packet_data == nullptr || packet_size <= 0 || result == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
AVPacket* packet = av_packet_alloc();
|
||||
if (packet == nullptr || av_new_packet(packet, packet_size) < 0) {
|
||||
av_packet_free(&packet);
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return false;
|
||||
}
|
||||
std::memcpy(packet->data, packet_data, static_cast<size_t>(packet_size));
|
||||
|
||||
int rc = avcodec_send_packet(m_codec_context, packet);
|
||||
av_packet_free(&packet);
|
||||
if (rc < 0) {
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
if (frame == nullptr) {
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = avcodec_receive_frame(m_codec_context, frame);
|
||||
if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF) {
|
||||
av_frame_free(&frame);
|
||||
return true;
|
||||
}
|
||||
if (rc < 0) {
|
||||
av_frame_free(&frame);
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
|
||||
AVFrame* converted = AjmConvertFfmpegFrame(frame, m_sample_encoding, result);
|
||||
av_frame_free(&frame);
|
||||
if (converted == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool ok =
|
||||
WriteFrame(converted, output, output_size, output_offset, gapless, result);
|
||||
av_frame_free(&converted);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const AVCodec* m_codec = nullptr;
|
||||
AVCodecContext* m_codec_context = nullptr;
|
||||
AVCodecParserContext* m_parser = nullptr;
|
||||
bool m_have_header = false;
|
||||
uint32_t m_bitrate = 0;
|
||||
AjmMp3FrameHeader m_last_header;
|
||||
};
|
||||
|
||||
} // namespace Libs::Audio::Ajm
|
||||
Reference in New Issue
Block a user