audio: parse NGS2 ATRAC9 waveform metadata

This commit is contained in:
nmzik
2026-08-24 08:42:07 +02:00
parent ffd07dbfce
commit 0b3302af70
+145 -10
View File
@@ -21,6 +21,8 @@
#include <limits>
#include <vector>
#include "libatrac9.h"
namespace Libs::Audio {
namespace {
@@ -1360,6 +1362,17 @@ namespace Ngs2 {
LIB_NAME("Ngs2", "Ngs2");
constexpr int32_t NGS2_ERROR_INVALID_OUT_ADDRESS =
static_cast<int32_t>(0x804a8010u);
constexpr int32_t NGS2_ERROR_INVALID_WAVEFORM_DATA =
static_cast<int32_t>(0x804a8430u);
constexpr int32_t NGS2_ERROR_INVALID_WAVEFORM_FORMAT =
static_cast<int32_t>(0x804a8431u);
constexpr int32_t NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT =
static_cast<int32_t>(0x804a8432u);
constexpr uint32_t NGS2_WAVEFORM_TYPE_ATRAC9 = 0x40;
struct Ngs2SystemOption {
size_t size = 0;
char name[64] = {};
@@ -1773,6 +1786,9 @@ static_assert(sizeof(Ngs2VoiceState) == 8);
static_assert(sizeof(Ngs2SubmixerVoiceState) == 20);
static_assert(sizeof(Ngs2CustomMasteringVoiceState) == 16);
static_assert(sizeof(Ngs2SamplerVoiceState) == 56);
static_assert(sizeof(Ngs2WaveformFormat) == 24);
static_assert(sizeof(Ngs2WaveformBlock) == 40);
static_assert(sizeof(Ngs2WaveformInfo) == 232);
static uint32_t Ngs2GetStateFlags(const Ngs2VoiceInternal* voice) {
switch (voice->state) {
@@ -2443,24 +2459,143 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
return OK;
}
static uint16_t Ngs2ReadLe16(const uint8_t* data) {
return static_cast<uint16_t>(data[0]) | (static_cast<uint16_t>(data[1]) << 8u);
}
static uint32_t Ngs2ReadLe32(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 Ngs2FourCcEquals(const uint8_t* data, const char* four_cc) {
return std::memcmp(data, four_cc, 4) == 0;
}
static int Ngs2ParseAtrac9Riff(const void* data, size_t data_size, Ngs2WaveformInfo* info) {
static constexpr uint8_t ATRAC9_GUID[16] = {0xd2, 0x42, 0xe1, 0x47, 0xba, 0x36,
0x8d, 0x4d, 0x88, 0xfc, 0x61, 0x65,
0x4f, 0x8c, 0x83, 0x6c};
const auto* bytes = static_cast<const uint8_t*>(data);
if (bytes == nullptr || data_size < 12) {
return NGS2_ERROR_INVALID_WAVEFORM_DATA;
}
if (!Ngs2FourCcEquals(bytes, "RIFF") || !Ngs2FourCcEquals(bytes + 8, "WAVE")) {
return NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT;
}
const uint64_t riff_end64 = 8ull + Ngs2ReadLe32(bytes + 4);
if (riff_end64 < 12 || riff_end64 > data_size) {
return NGS2_ERROR_INVALID_WAVEFORM_DATA;
}
const auto riff_end = static_cast<size_t>(riff_end64);
const uint8_t* format = nullptr;
const uint8_t* fact = nullptr;
size_t waveform_offset = 0;
uint32_t waveform_size = 0;
for (size_t offset = 12; offset + 8 <= riff_end;) {
const auto* chunk = bytes + offset;
const auto chunk_size = static_cast<size_t>(Ngs2ReadLe32(chunk + 4));
const auto payload = offset + 8;
if (chunk_size > riff_end - payload) {
return NGS2_ERROR_INVALID_WAVEFORM_DATA;
}
if (Ngs2FourCcEquals(chunk, "fmt ")) {
if (chunk_size < 52) {
return NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
}
format = bytes + payload;
} else if (Ngs2FourCcEquals(chunk, "fact")) {
if (chunk_size < 12) {
return NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
}
fact = bytes + payload;
} else if (Ngs2FourCcEquals(chunk, "data")) {
if (payload > std::numeric_limits<uint32_t>::max()) {
return NGS2_ERROR_INVALID_WAVEFORM_DATA;
}
waveform_offset = payload;
waveform_size = static_cast<uint32_t>(chunk_size);
}
const uint64_t next = static_cast<uint64_t>(payload) + chunk_size + (chunk_size & 1u);
if (next > riff_end) {
return NGS2_ERROR_INVALID_WAVEFORM_DATA;
}
offset = static_cast<size_t>(next);
}
if (format == nullptr || fact == nullptr || waveform_offset == 0) {
return NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
}
if (Ngs2ReadLe16(format) != 0xfffe ||
std::memcmp(format + 24, ATRAC9_GUID, sizeof(ATRAC9_GUID)) != 0) {
return NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT;
}
std::array<uint8_t, ATRAC9_CONFIG_DATA_SIZE> config {};
std::memcpy(config.data(), format + 44, config.size());
if (config[0] != 0xfe || (config[1] & 1u) != 0 || ((config[1] >> 1u) & 7u) >= 6u) {
return NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
}
Atrac9CodecInfo codec {};
void* decoder = Atrac9GetHandle();
const bool valid_codec =
decoder != nullptr && Atrac9InitDecoder(decoder, config.data()) == 0 &&
Atrac9GetCodecInfo(decoder, &codec) == 0 && codec.channels > 0 &&
codec.samplingRate > 0 && codec.superframeSize > 0 && codec.framesInSuperframe > 0 &&
codec.frameSamples > 0 && codec.superframeSize % codec.framesInSuperframe == 0;
if (decoder != nullptr) {
Atrac9ReleaseHandle(decoder);
}
const uint64_t frame_samples =
static_cast<uint64_t>(codec.frameSamples) * static_cast<uint64_t>(codec.framesInSuperframe);
if (!valid_codec || codec.channels != Ngs2ReadLe16(format + 2) ||
codec.samplingRate != static_cast<int>(Ngs2ReadLe32(format + 4)) ||
codec.superframeSize != Ngs2ReadLe16(format + 12) ||
frame_samples != Ngs2ReadLe16(format + 18) ||
frame_samples > std::numeric_limits<uint32_t>::max()) {
return NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
}
info->format.waveform_type = NGS2_WAVEFORM_TYPE_ATRAC9;
info->format.num_channels = static_cast<uint32_t>(codec.channels);
info->format.sample_rate = static_cast<uint32_t>(codec.samplingRate);
std::memcpy(&info->format.config_data, config.data(), config.size());
info->data_offset = static_cast<uint32_t>(waveform_offset);
info->data_size = waveform_size;
info->num_samples = Ngs2ReadLe32(fact);
info->audio_unit_size = static_cast<uint32_t>(codec.superframeSize /
codec.framesInSuperframe);
info->num_audio_unit_samples = static_cast<uint32_t>(codec.frameSamples);
info->num_audio_unit_per_frame = static_cast<uint32_t>(codec.framesInSuperframe);
info->audio_frame_size = static_cast<uint32_t>(codec.superframeSize);
info->num_audio_frame_samples = static_cast<uint32_t>(frame_samples);
info->num_delay_samples = Ngs2ReadLe32(fact + 4);
info->num_blocks = 1;
info->blocks[0].data_offset = waveform_offset;
info->blocks[0].data_size = waveform_size;
info->blocks[0].num_skip_samples = Ngs2ReadLe32(fact + 8);
info->blocks[0].num_samples = info->num_samples;
return OK;
}
int KYTY_SYSV_ABI Ngs2ParseWaveformData(const void* data, size_t data_size,
Ngs2WaveformInfo* info) {
PRINT_NAME();
LOGF("\t data = 0x%016" PRIx64 ", data_size = 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(data), static_cast<uint64_t>(data_size));
EXIT_NOT_IMPLEMENTED(info == nullptr);
if (info == nullptr) {
return NGS2_ERROR_INVALID_OUT_ADDRESS;
}
std::memset(info, 0, sizeof(Ngs2WaveformInfo));
info->format.waveform_type = 0x80;
info->format.num_channels = 1;
info->format.sample_rate = 48000;
info->data_size =
static_cast<uint32_t>(std::min<size_t>(data_size, std::numeric_limits<uint32_t>::max()));
info->num_audio_unit_samples = 1;
info->num_audio_unit_per_frame = 1;
info->num_audio_frame_samples = 1;
return OK;
return Ngs2ParseAtrac9Riff(data, data_size, info);
}
int KYTY_SYSV_ABI Ngs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_t sample_pos,