Files
AC6_recomp/thirdparty/rexglue-sdk/src/native/audio/xma/context.cpp
T
Dipshet 8c922abd77 Preserve the XMA stream timeline across undecodable frames
A frame the decoder backend rejects advances the read offset but emitted nothing, silently shortening that stream by 512 samples, permanent desync for multi-stream sources (5.1 premixes as parallel stereo XMA). Emit the frame as silence instead so the timeline holds. Robustness: no in-game trigger known in AC6. audio_xma_preserve_timeline (default on).
2026-07-31 04:23:20 +02:00

934 lines
34 KiB
C++

/**
* ReXGlue native audio runtime
* Part of the AC6 Recompilation project
*/
#include <algorithm>
#include <cstring>
#include <span>
#include <native/audio/xma/context.h>
#include <native/audio/xma/xma_decoder_backend.h>
#include <native/audio/xma/helpers.h>
#include <rex/cvar.h>
#include <rex/dbg.h>
#include <rex/logging.h>
#include <native/memory/ring_buffer.h>
#include <native/stream.h>
REXCVAR_DECLARE(bool, audio_deep_trace);
REXCVAR_DECLARE(bool, audio_xma_preserve_timeline);
namespace rex::audio {
using stream::BitStream;
namespace {
bool IsDeepTraceEnabled() {
return REXCVAR_GET(audio_deep_trace);
}
} // namespace
XmaContext::XmaContext()
: work_completion_event_(rex::thread::Event::CreateAutoResetEvent(false)) {}
XmaContext::~XmaContext() = default;
int XmaContext::Setup(uint32_t id, memory::Memory* memory, uint32_t guest_ptr) {
id_ = id;
memory_ = memory;
guest_ptr_ = guest_ptr;
ResetRuntimeStateLocked();
decoder_backend_ = xma::CreateXmaDecoderBackend();
if (!decoder_backend_ || !decoder_backend_->IsAvailable()) {
REXAPU_ERROR("XmaContext {}: XMA decoder backend unavailable", id);
return 1;
}
return 0;
}
bool XmaContext::Save(stream::ByteStream* stream) {
if (!stream || !memory_ || !guest_ptr_) {
return false;
}
std::lock_guard<std::mutex> lock(lock_);
auto* context_ptr = memory_->TranslateVirtual(guest_ptr_);
if (!context_ptr) {
return false;
}
stream->Write(guest_ptr_);
stream->Write(static_cast<uint32_t>(is_allocated() ? 1 : 0));
stream->Write(static_cast<uint32_t>(is_enabled() ? 1 : 0));
stream->Write(context_ptr, sizeof(XMA_CONTEXT_DATA));
stream->Write(input_buffer_.data(), input_buffer_.size());
stream->Write(xma_frame_.data(), xma_frame_.size());
stream->Write(raw_frame_.data(), raw_frame_.size());
stream->Write(remaining_subframe_blocks_in_output_buffer_);
stream->Write(current_frame_remaining_subframes_);
stream->Write(loop_frame_output_limit_);
stream->Write(static_cast<uint32_t>(loop_start_skip_pending_ ? 1 : 0));
stream->Write(decode_attempt_count_);
stream->Write(last_input_read_offset_before_);
stream->Write(last_input_read_offset_after_);
stream->Write(last_current_input_packet_count_);
stream->Write(last_frame_size_bits_);
stream->Write(last_bits_to_copy_);
stream->Write(last_next_packet_index_);
stream->Write(last_current_buffer_);
stream->Write(last_skip_count_);
stream->Write(last_packet_index_);
stream->Write(static_cast<uint32_t>(last_cross_packet_copy_ ? 1 : 0));
stream->Write(static_cast<uint32_t>(last_swapped_input_buffer_ ? 1 : 0));
stream->Write(static_cast<uint32_t>(last_decode_succeeded_ ? 1 : 0));
stream->Write(last_error_status_);
return true;
}
bool XmaContext::Restore(stream::ByteStream* stream) {
if (!stream || !memory_ || !guest_ptr_) {
return false;
}
std::lock_guard<std::mutex> lock(lock_);
const uint32_t saved_guest_ptr = stream->Read<uint32_t>();
if (saved_guest_ptr != guest_ptr_) {
REXAPU_ERROR("XmaContext {}: restore guest_ptr mismatch saved={:08X} live={:08X}", id(),
saved_guest_ptr, guest_ptr_);
return false;
}
const bool saved_allocated = stream->Read<uint32_t>() != 0;
const bool saved_enabled = stream->Read<uint32_t>() != 0;
auto* context_ptr = memory_->TranslateVirtual(guest_ptr_);
if (!context_ptr) {
return false;
}
stream->Read(context_ptr, sizeof(XMA_CONTEXT_DATA));
stream->Read(input_buffer_.data(), input_buffer_.size());
stream->Read(xma_frame_.data(), xma_frame_.size());
stream->Read(raw_frame_.data(), raw_frame_.size());
remaining_subframe_blocks_in_output_buffer_ = stream->Read<int32_t>();
current_frame_remaining_subframes_ = stream->Read<uint8_t>();
loop_frame_output_limit_ = stream->Read<uint8_t>();
loop_start_skip_pending_ = stream->Read<uint32_t>() != 0;
decode_attempt_count_ = stream->Read<uint64_t>();
last_input_read_offset_before_ = stream->Read<uint32_t>();
last_input_read_offset_after_ = stream->Read<uint32_t>();
last_current_input_packet_count_ = stream->Read<uint32_t>();
last_frame_size_bits_ = stream->Read<uint32_t>();
last_bits_to_copy_ = stream->Read<uint32_t>();
last_next_packet_index_ = stream->Read<uint32_t>();
last_current_buffer_ = stream->Read<uint8_t>();
last_skip_count_ = stream->Read<uint8_t>();
last_packet_index_ = stream->Read<int32_t>();
last_cross_packet_copy_ = stream->Read<uint32_t>() != 0;
last_swapped_input_buffer_ = stream->Read<uint32_t>() != 0;
last_decode_succeeded_ = stream->Read<uint32_t>() != 0;
last_error_status_ = stream->Read<uint32_t>();
set_is_allocated(saved_allocated);
set_is_enabled(saved_enabled);
if (!saved_allocated) {
std::memset(context_ptr, 0, sizeof(XMA_CONTEXT_DATA));
ResetRuntimeStateLocked();
}
return true;
}
bool XmaContext::Work() {
if (!is_allocated() || !is_enabled()) {
return false;
}
std::lock_guard<std::mutex> lock(lock_);
set_is_enabled(false);
auto context_ptr = memory()->TranslateVirtual(guest_ptr());
XMA_CONTEXT_DATA data(context_ptr);
const XMA_CONTEXT_DATA initial_data = data;
if (!data.output_buffer_valid) {
return true;
}
memory::RingBuffer output_rb = PrepareOutputRingBuffer(&data);
if (data.IsConsumeOnlyContext()) {
if (current_frame_remaining_subframes_ == 0) {
return true;
}
Consume(&output_rb, &data);
data.output_buffer_write_offset = output_rb.write_offset() / kOutputBytesPerBlock;
StoreContextMerged(data, initial_data, context_ptr);
return true;
}
const uint32_t effective_sdc = std::max(static_cast<uint32_t>(1), data.subframe_decode_count);
const int32_t minimum_subframe_decode_count =
static_cast<int32_t>(effective_sdc) + data.output_buffer_padding;
if (minimum_subframe_decode_count > remaining_subframe_blocks_in_output_buffer_) {
StoreContextMerged(data, initial_data, context_ptr);
return true;
}
while (remaining_subframe_blocks_in_output_buffer_ >= minimum_subframe_decode_count) {
Decode(&data);
Consume(&output_rb, &data);
if (!data.IsAnyInputBufferValid() || data.error_status == 4) {
if (data.error_status == 4) {
REXAPU_WARN(
"XmaContext {}: decode aborted with error_status=4 packet_index={} next_packet={} "
"read_before={} read_after={} frame_bits={} bits_to_copy={} skip={} "
"cross_packet={} swapped={}",
id(), last_packet_index_, last_next_packet_index_, last_input_read_offset_before_,
last_input_read_offset_after_, last_frame_size_bits_, last_bits_to_copy_,
last_skip_count_, last_cross_packet_copy_, last_swapped_input_buffer_);
}
break;
}
}
data.output_buffer_write_offset = output_rb.write_offset() / kOutputBytesPerBlock;
if (output_rb.empty()) {
data.output_buffer_valid = 0;
}
StoreContextMerged(data, initial_data, context_ptr);
return true;
}
void XmaContext::Enable() {
std::lock_guard<std::mutex> lock(lock_);
set_is_enabled(true);
}
bool XmaContext::Block(bool poll) {
if (!lock_.try_lock()) {
if (poll) {
return false;
}
lock_.lock();
}
lock_.unlock();
return true;
}
bool XmaContext::IsIdle() {
std::lock_guard<std::mutex> lock(lock_);
if (!is_allocated() || !memory_ || !guest_ptr_) {
return true;
}
auto* context_ptr = memory_->TranslateVirtual(guest_ptr_);
if (!context_ptr) {
return true;
}
const XMA_CONTEXT_DATA context(context_ptr);
return (!context.input_buffer_0_valid && !context.input_buffer_1_valid) ||
!context.work_buffer_ptr;
}
void XmaContext::Clear() {
std::lock_guard<std::mutex> lock(lock_);
auto context_ptr = memory()->TranslateVirtual(guest_ptr());
XMA_CONTEXT_DATA data(context_ptr);
REXAPU_DEBUG(
"XmaContext {} guest-state before reset: current_buffer={} valid0={} valid1={} "
"output_valid={} input_read_offset={} output_read_offset={} output_write_offset={} "
"error_status={}",
id(), static_cast<uint32_t>(data.current_buffer),
static_cast<uint32_t>(data.input_buffer_0_valid),
static_cast<uint32_t>(data.input_buffer_1_valid),
static_cast<uint32_t>(data.output_buffer_valid),
static_cast<uint32_t>(data.input_buffer_read_offset),
static_cast<uint32_t>(data.output_buffer_read_offset),
static_cast<uint32_t>(data.output_buffer_write_offset),
static_cast<uint32_t>(data.error_status));
REXAPU_DEBUG(
"XmaContext {} last decode snapshot before reset: current_buffer={} read_before={} "
"read_after={} packet_count={} decode_attempt={} last_packet={} next_packet={} "
"last_frame_bits={} bits_to_copy={} skip={} cross_packet={} swapped={} "
"decode_ok={} last_error_status={}",
id(), last_current_buffer_, last_input_read_offset_before_, last_input_read_offset_after_,
last_current_input_packet_count_, decode_attempt_count_, last_packet_index_,
last_next_packet_index_, last_frame_size_bits_, last_bits_to_copy_, last_skip_count_,
last_cross_packet_copy_, last_swapped_input_buffer_, last_decode_succeeded_,
last_error_status_);
ClearLocked(&data);
data.Store(context_ptr);
}
void XmaContext::ClearLocked(XMA_CONTEXT_DATA* data) {
data->input_buffer_0_valid = 0;
data->input_buffer_1_valid = 0;
data->output_buffer_valid = 0;
data->input_buffer_read_offset = kBitsPerPacketHeader;
data->output_buffer_read_offset = 0;
data->output_buffer_write_offset = 0;
ResetRuntimeStateLocked();
}
void XmaContext::ResetRuntimeStateLocked() {
input_buffer_.fill(0);
xma_frame_.fill(0);
raw_frame_.fill(0);
current_frame_remaining_subframes_ = 0;
remaining_subframe_blocks_in_output_buffer_ = 0;
loop_frame_output_limit_ = 0;
loop_start_skip_pending_ = false;
decode_attempt_count_ = 0;
last_input_read_offset_before_ = 0;
last_input_read_offset_after_ = 0;
last_current_input_packet_count_ = 0;
last_frame_size_bits_ = 0;
last_bits_to_copy_ = 0;
last_next_packet_index_ = 0;
last_current_buffer_ = 0;
last_skip_count_ = 0;
last_packet_index_ = -1;
last_cross_packet_copy_ = false;
last_swapped_input_buffer_ = false;
last_decode_succeeded_ = false;
last_error_status_ = 0;
}
void XmaContext::Disable() {
std::lock_guard<std::mutex> lock(lock_);
set_is_enabled(false);
}
void XmaContext::Release() {
std::lock_guard<std::mutex> lock(lock_);
assert_true(is_allocated());
set_is_enabled(false);
set_is_allocated(false);
ResetRuntimeStateLocked();
auto context_ptr = memory()->TranslateVirtual(guest_ptr());
std::memset(context_ptr, 0, sizeof(XMA_CONTEXT_DATA));
}
void XmaContext::SwapInputBuffer(XMA_CONTEXT_DATA* data) {
if (data->current_buffer == 0) {
data->input_buffer_0_valid = 0;
} else {
data->input_buffer_1_valid = 0;
}
data->current_buffer ^= 1;
data->input_buffer_read_offset = kBitsPerPacketHeader;
}
void XmaContext::UpdateLoopStatus(XMA_CONTEXT_DATA* data) {
if (data->loop_count == 0) {
return;
}
const uint32_t loop_start = std::max(kBitsPerPacketHeader, data->loop_start);
const uint32_t loop_end = std::max(kBitsPerPacketHeader, data->loop_end);
if (data->input_buffer_read_offset != loop_end) {
return;
}
data->input_buffer_read_offset = loop_start;
loop_start_skip_pending_ = true;
if (data->loop_count < 255) {
data->loop_count--;
}
}
int XmaContext::GetSampleRate(int id) {
return kIdToSampleRate[std::min(id, 3)];
}
int16_t XmaContext::GetPacketNumber(size_t size, size_t bit_offset) {
if (bit_offset < kBitsPerPacketHeader) {
assert_always();
return -1;
}
if (bit_offset >= (size << 3)) {
assert_always();
return -1;
}
size_t byte_offset = bit_offset >> 3;
size_t packet_number = byte_offset / kBytesPerPacket;
return static_cast<int16_t>(packet_number);
}
uint32_t XmaContext::GetCurrentInputBufferSize(XMA_CONTEXT_DATA* data) {
return data->GetCurrentInputBufferPacketCount() * kBytesPerPacket;
}
uint8_t* XmaContext::GetCurrentInputBuffer(XMA_CONTEXT_DATA* data) {
return memory()->TranslatePhysical(data->GetCurrentInputBufferAddress());
}
uint32_t XmaContext::GetAmountOfBitsToRead(uint32_t remaining_stream_bits, uint32_t frame_size) {
return std::min(remaining_stream_bits, frame_size);
}
const uint8_t* XmaContext::GetNextPacket(XMA_CONTEXT_DATA* data, uint32_t next_packet_index,
uint32_t current_input_packet_count) {
if (next_packet_index < current_input_packet_count) {
return memory()->TranslatePhysical(data->GetCurrentInputBufferAddress()) +
next_packet_index * kBytesPerPacket;
}
const uint8_t next_buffer_index = data->current_buffer ^ 1;
if (!data->IsInputBufferValid(next_buffer_index)) {
return nullptr;
}
const uint32_t next_buffer_packet_count = data->GetInputBufferPacketCount(next_buffer_index);
const uint32_t next_buffer_packet_index = next_packet_index - current_input_packet_count;
if (next_buffer_packet_index >= next_buffer_packet_count) {
return nullptr;
}
const uint32_t next_buffer_address = data->GetInputBufferAddress(next_buffer_index);
if (!next_buffer_address) {
REXAPU_ERROR("XmaContext {}: Buffer marked valid but has null pointer!", id());
return nullptr;
}
return memory()->TranslatePhysical(next_buffer_address) +
next_buffer_packet_index * kBytesPerPacket;
}
uint32_t XmaContext::GetNextPacketReadOffset(XMA_CONTEXT_DATA* data, uint32_t next_packet_index,
uint32_t current_input_packet_count) {
if (next_packet_index < current_input_packet_count) {
uint8_t* buffer = memory()->TranslatePhysical(data->GetCurrentInputBufferAddress());
while (next_packet_index < current_input_packet_count) {
uint8_t* next_packet = buffer + (next_packet_index * kBytesPerPacket);
const uint32_t packet_frame_offset = xma::GetPacketFrameOffset(next_packet);
if (packet_frame_offset <= kMaxFrameSizeinBits) {
return (next_packet_index * kBitsPerPacket) + packet_frame_offset;
}
next_packet_index++;
}
return kBitsPerPacketHeader;
}
const uint8_t next_buffer_index = data->current_buffer ^ 1;
if (!data->IsInputBufferValid(next_buffer_index)) {
return kBitsPerPacketHeader;
}
const uint32_t next_buffer_address = data->GetInputBufferAddress(next_buffer_index);
if (!next_buffer_address) {
REXAPU_ERROR("XmaContext {}: Buffer marked valid but has null pointer!", id());
return kBitsPerPacketHeader;
}
uint32_t next_buffer_packet_index = next_packet_index - current_input_packet_count;
const uint32_t next_buffer_packet_count = data->GetInputBufferPacketCount(next_buffer_index);
uint8_t* next_buffer = memory()->TranslatePhysical(next_buffer_address);
while (next_buffer_packet_index < next_buffer_packet_count) {
uint8_t* next_packet = next_buffer + (next_buffer_packet_index * kBytesPerPacket);
const uint32_t packet_frame_offset = xma::GetPacketFrameOffset(next_packet);
if (packet_frame_offset <= kMaxFrameSizeinBits) {
return (next_buffer_packet_index * kBitsPerPacket) + packet_frame_offset;
}
next_buffer_packet_index++;
}
return kBitsPerPacketHeader;
}
memory::RingBuffer XmaContext::PrepareOutputRingBuffer(XMA_CONTEXT_DATA* data) {
const uint32_t output_capacity = data->output_buffer_block_count * kOutputBytesPerBlock;
const uint32_t output_read_offset = data->output_buffer_read_offset * kOutputBytesPerBlock;
const uint32_t output_write_offset = data->output_buffer_write_offset * kOutputBytesPerBlock;
if (output_capacity > kOutputMaxSizeBytes) {
REXAPU_WARN(
"XmaContext {}: Output buffer exceeds expected size! "
"(Actual: {} Max: {})",
id(), output_capacity, kOutputMaxSizeBytes);
}
uint8_t* output_buffer = memory()->TranslatePhysical(data->output_buffer_ptr);
memory::RingBuffer output_rb(output_buffer, output_capacity);
output_rb.set_read_offset(output_read_offset);
output_rb.set_write_offset(output_write_offset);
remaining_subframe_blocks_in_output_buffer_ =
static_cast<int32_t>(output_rb.write_count()) / kOutputBytesPerBlock;
return output_rb;
}
kPacketInfo XmaContext::GetPacketInfo(uint8_t* packet, uint32_t frame_offset) {
kPacketInfo packet_info = {};
const uint32_t first_frame_offset = xma::GetPacketFrameOffset(packet);
BitStream stream(packet, kBitsPerPacket);
stream.SetOffset(first_frame_offset);
if (frame_offset < first_frame_offset) {
packet_info.current_frame_ = 0;
packet_info.current_frame_size_ = first_frame_offset - frame_offset;
}
while (true) {
if (stream.BitsRemaining() < kBitsPerFrameHeader) {
break;
}
const uint64_t frame_size = stream.Peek(kBitsPerFrameHeader);
if (frame_size == 0 || frame_size == xma::kMaxFrameLength) {
break;
}
if (stream.offset_bits() == frame_offset) {
packet_info.current_frame_ = packet_info.frame_count_;
packet_info.current_frame_size_ = static_cast<uint32_t>(frame_size);
}
packet_info.frame_count_++;
if (frame_size > stream.BitsRemaining()) {
break;
}
stream.Advance(frame_size - 1);
if (stream.Read(1) == 0) {
break;
}
}
if (xma::IsPacketXma2Type(packet)) {
const uint8_t xma2_frame_count = xma::GetPacketFrameCount(packet);
if (xma2_frame_count > packet_info.frame_count_) {
if (packet_info.current_frame_size_ == 0) {
packet_info.current_frame_ = packet_info.frame_count_;
}
packet_info.frame_count_ = xma2_frame_count;
}
}
return packet_info;
}
void XmaContext::StoreContextMerged(const XMA_CONTEXT_DATA& data,
const XMA_CONTEXT_DATA& initial_data, uint8_t* context_ptr) {
XMA_CONTEXT_DATA fresh(context_ptr);
fresh.loop_count = data.loop_count;
fresh.output_buffer_write_offset = data.output_buffer_write_offset;
if (initial_data.input_buffer_0_valid && !data.input_buffer_0_valid) {
fresh.input_buffer_0_valid = 0;
}
if (initial_data.input_buffer_1_valid && !data.input_buffer_1_valid) {
fresh.input_buffer_1_valid = 0;
}
if (initial_data.output_buffer_valid && !data.output_buffer_valid) {
fresh.output_buffer_valid = 0;
}
fresh.input_buffer_read_offset = data.input_buffer_read_offset;
fresh.error_status = data.error_status;
fresh.current_buffer = data.current_buffer;
fresh.output_buffer_read_offset = data.output_buffer_read_offset;
fresh.Store(context_ptr);
}
void XmaContext::Consume(memory::RingBuffer* output_rb, const XMA_CONTEXT_DATA* data) {
if (!current_frame_remaining_subframes_) {
return;
}
if (loop_frame_output_limit_ > 0) {
const uint8_t total_subframes =
(kBytesPerFrameChannel / kOutputBytesPerBlock) << data->is_stereo;
const uint8_t consumed = total_subframes - current_frame_remaining_subframes_;
if (consumed >= loop_frame_output_limit_) {
remaining_subframe_blocks_in_output_buffer_ -= data->output_buffer_padding;
current_frame_remaining_subframes_ = 0;
loop_frame_output_limit_ = 0;
return;
}
}
const uint8_t effective_sdc = std::max(static_cast<uint32_t>(1), data->subframe_decode_count);
int8_t subframes_to_write = std::min(static_cast<int8_t>(current_frame_remaining_subframes_),
static_cast<int8_t>(effective_sdc));
if (loop_frame_output_limit_ > 0) {
const uint8_t total_subframes =
(kBytesPerFrameChannel / kOutputBytesPerBlock) << data->is_stereo;
const uint8_t consumed = total_subframes - current_frame_remaining_subframes_;
const int8_t remaining_until_limit = static_cast<int8_t>(loop_frame_output_limit_ - consumed);
if (subframes_to_write > remaining_until_limit) {
subframes_to_write = remaining_until_limit;
}
}
const int8_t raw_frame_read_offset =
((kBytesPerFrameChannel / kOutputBytesPerBlock) << data->is_stereo) -
current_frame_remaining_subframes_;
output_rb->Write(raw_frame_.data() + (kOutputBytesPerBlock * raw_frame_read_offset),
subframes_to_write * kOutputBytesPerBlock);
const int8_t headroom = (current_frame_remaining_subframes_ - subframes_to_write == 0)
? data->output_buffer_padding
: 0;
remaining_subframe_blocks_in_output_buffer_ -= subframes_to_write + headroom;
current_frame_remaining_subframes_ -= subframes_to_write;
}
size_t XmaContext::PreparePacket(uint32_t frame_size, uint32_t frame_padding) {
const size_t packet_size = 1 + ((frame_padding + frame_size) / 8) +
(((frame_padding + frame_size) % 8) ? 1 : 0);
auto padding_end = packet_size * 8 - (8 + frame_padding + frame_size);
assert_true(padding_end < 8);
xma_frame_[0] = ((frame_padding & 7) << 5) | ((padding_end & 7) << 2);
return packet_size;
}
void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
SCOPE_profile_cpu_f("apu");
++decode_attempt_count_;
last_input_read_offset_before_ = static_cast<uint32_t>(data->input_buffer_read_offset);
last_input_read_offset_after_ = static_cast<uint32_t>(data->input_buffer_read_offset);
last_current_input_packet_count_ = 0;
last_frame_size_bits_ = 0;
last_bits_to_copy_ = 0;
last_next_packet_index_ = 0;
last_current_buffer_ = data->current_buffer;
last_skip_count_ = 0;
last_packet_index_ = -1;
last_cross_packet_copy_ = false;
last_swapped_input_buffer_ = false;
last_decode_succeeded_ = false;
last_error_status_ = static_cast<uint32_t>(data->error_status);
auto log_decode_state = [&](const char* reason) {
REXAPU_WARN(
"XmaContext {}: {} current_buffer={} valid0={} valid1={} output_valid={} "
"read_before={} read_after={} packet_index={} next_packet={} packet_count={} "
"skip={} frame_bits={} bits_to_copy={} loop_count={} err={} cross_packet={} "
"swapped={} decode_attempt={}",
id(), reason, static_cast<uint32_t>(data->current_buffer),
static_cast<uint32_t>(data->input_buffer_0_valid),
static_cast<uint32_t>(data->input_buffer_1_valid),
static_cast<uint32_t>(data->output_buffer_valid), last_input_read_offset_before_,
last_input_read_offset_after_,
last_packet_index_, last_next_packet_index_, last_current_input_packet_count_,
last_skip_count_, last_frame_size_bits_, last_bits_to_copy_,
static_cast<uint32_t>(data->loop_count), static_cast<uint32_t>(data->error_status),
last_cross_packet_copy_, last_swapped_input_buffer_, decode_attempt_count_);
};
if (!data->IsAnyInputBufferValid()) {
return;
}
if (current_frame_remaining_subframes_ > 0) {
return;
}
if (!data->IsCurrentInputBufferValid()) {
last_swapped_input_buffer_ = true;
SwapInputBuffer(data);
if (!data->IsCurrentInputBufferValid()) {
last_input_read_offset_after_ = static_cast<uint32_t>(data->input_buffer_read_offset);
return;
}
}
uint8_t* current_input_buffer = GetCurrentInputBuffer(data);
input_buffer_.fill(0);
bool is_loop_end_frame = false;
if (data->loop_count > 0) {
const uint32_t loop_end = std::max(kBitsPerPacketHeader, data->loop_end);
is_loop_end_frame = (data->input_buffer_read_offset == loop_end);
}
UpdateLoopStatus(data);
if (!data->output_buffer_block_count) {
REXAPU_ERROR("XmaContext {}: Error - Received 0 for output_buffer_block_count!", id());
return;
}
if (data->input_buffer_read_offset < kBitsPerPacketHeader) {
data->input_buffer_read_offset = kBitsPerPacketHeader;
}
const uint32_t current_input_size = GetCurrentInputBufferSize(data);
const uint32_t current_input_packet_count = current_input_size / kBytesPerPacket;
last_current_input_packet_count_ = current_input_packet_count;
const int16_t packet_index = GetPacketNumber(current_input_size, data->input_buffer_read_offset);
last_packet_index_ = packet_index;
if (packet_index == -1) {
REXAPU_ERROR("XmaContext {}: Invalid packet index. Input read offset: {}", id(),
static_cast<uint32_t>(data->input_buffer_read_offset));
log_decode_state("invalid-packet-index");
return;
}
auto skip_corrupt_packet = [&](const char* reason) {
data->error_status = 4;
last_error_status_ = static_cast<uint32_t>(data->error_status);
const uint32_t next_packet_index = packet_index + 1;
const bool next_packet_in_next_buffer = next_packet_index >= current_input_packet_count;
uint32_t next_input_offset =
GetNextPacketReadOffset(data, next_packet_index, current_input_packet_count);
if (next_packet_in_next_buffer || next_input_offset == kBitsPerPacketHeader) {
last_swapped_input_buffer_ = true;
SwapInputBuffer(data);
}
data->input_buffer_read_offset = next_input_offset;
last_input_read_offset_after_ = static_cast<uint32_t>(data->input_buffer_read_offset);
log_decode_state(reason);
};
uint8_t* packet = current_input_buffer + (packet_index * kBytesPerPacket);
const uint32_t packet_first_frame_offset = xma::GetPacketFrameOffset(packet);
if (packet_first_frame_offset > kMaxFrameSizeinBits) {
skip_corrupt_packet("packet-frame-offset-invalid");
return;
}
uint32_t relative_offset = data->input_buffer_read_offset % kBitsPerPacket;
if (relative_offset < packet_first_frame_offset) {
data->input_buffer_read_offset = (packet_index * kBitsPerPacket) + packet_first_frame_offset;
relative_offset = packet_first_frame_offset;
}
const uint8_t skip_count = xma::GetPacketSkipCount(packet);
last_skip_count_ = skip_count;
if (skip_count == 0xFF) {
const uint32_t next_packet_index = packet_index + 1;
const bool next_packet_in_next_buffer = next_packet_index >= current_input_packet_count;
uint32_t next_input_offset =
GetNextPacketReadOffset(data, next_packet_index, current_input_packet_count);
if (next_packet_in_next_buffer || next_input_offset == kBitsPerPacketHeader) {
last_swapped_input_buffer_ = true;
SwapInputBuffer(data);
}
data->input_buffer_read_offset = next_input_offset;
last_input_read_offset_after_ = next_input_offset;
if (IsDeepTraceEnabled()) {
REXAPU_DEBUG(
"XmaContext {}: skip packet packet_index={} next_input_offset={} packet_count={}",
id(), packet_index, next_input_offset, current_input_packet_count);
}
return;
}
kPacketInfo packet_info = GetPacketInfo(packet, relative_offset);
const uint32_t packet_to_skip = skip_count + 1;
const uint32_t next_packet_index = packet_index + packet_to_skip;
last_next_packet_index_ = next_packet_index;
if (packet_info.current_frame_size_ == 0) {
const uint8_t* next_packet = GetNextPacket(data, next_packet_index, current_input_packet_count);
if (!next_packet) {
last_swapped_input_buffer_ = true;
SwapInputBuffer(data);
last_input_read_offset_after_ = static_cast<uint32_t>(data->input_buffer_read_offset);
log_decode_state("missing-next-packet-for-split-frame");
return;
}
last_cross_packet_copy_ = true;
std::memcpy(input_buffer_.data(), packet + kBytesPerPacketHeader, kBytesPerPacketData);
std::memcpy(input_buffer_.data() + kBytesPerPacketData, next_packet + kBytesPerPacketHeader,
kBytesPerPacketData);
BitStream combined(input_buffer_.data(), (kBitsPerPacket - kBitsPerPacketHeader) * 2);
combined.SetOffset(relative_offset - kBitsPerPacketHeader);
uint64_t frame_size = combined.Peek(kBitsPerFrameHeader);
if (frame_size == xma::kMaxFrameLength) {
data->error_status = 4;
last_error_status_ = static_cast<uint32_t>(data->error_status);
log_decode_state("split-frame-size-invalid");
return;
}
packet_info.current_frame_size_ = static_cast<uint32_t>(frame_size);
}
last_frame_size_bits_ = packet_info.current_frame_size_;
const uint32_t combined_payload_bits = (kBitsPerPacket - kBitsPerPacketHeader) * 2;
const uint32_t combined_relative_offset = relative_offset - kBitsPerPacketHeader;
if (packet_info.current_frame_size_ == 0 ||
combined_relative_offset > combined_payload_bits ||
packet_info.current_frame_size_ > (combined_payload_bits - combined_relative_offset)) {
skip_corrupt_packet("frame-size-out-of-range");
return;
}
BitStream stream(current_input_buffer, (packet_index + 1) * kBitsPerPacket);
stream.SetOffset(data->input_buffer_read_offset);
const uint64_t bits_to_copy =
GetAmountOfBitsToRead(static_cast<uint32_t>(stream.BitsRemaining()),
packet_info.current_frame_size_);
last_bits_to_copy_ = static_cast<uint32_t>(bits_to_copy);
if (bits_to_copy == 0) {
REXAPU_ERROR("XmaContext {}: There are no bits to copy!", id());
last_swapped_input_buffer_ = true;
SwapInputBuffer(data);
last_input_read_offset_after_ = static_cast<uint32_t>(data->input_buffer_read_offset);
log_decode_state("zero-bits-to-copy");
return;
}
if (packet_info.isLastFrameInPacket()) {
if (stream.BitsRemaining() < packet_info.current_frame_size_) {
const uint8_t* next_packet =
GetNextPacket(data, next_packet_index, current_input_packet_count);
if (!next_packet) {
data->error_status = 4;
last_error_status_ = static_cast<uint32_t>(data->error_status);
log_decode_state("missing-next-packet-last-frame");
return;
}
last_cross_packet_copy_ = true;
std::memcpy(input_buffer_.data() + kBytesPerPacketData, next_packet + kBytesPerPacketHeader,
kBytesPerPacketData);
}
}
std::memcpy(input_buffer_.data(), packet + kBytesPerPacketHeader, kBytesPerPacketData);
stream = BitStream(input_buffer_.data(), (kBitsPerPacket - kBitsPerPacketHeader) * 2);
stream.SetOffset(relative_offset - kBitsPerPacketHeader);
xma_frame_.fill(0);
const uint32_t padding_start =
static_cast<uint8_t>(stream.Copy(xma_frame_.data() + 1, packet_info.current_frame_size_));
raw_frame_.fill(0);
if (IsDeepTraceEnabled() &&
(last_cross_packet_copy_ || (decode_attempt_count_ % 512) == 0)) {
REXAPU_DEBUG(
"XmaContext {}: decode candidate packet_index={} next_packet={} frame_bits={} "
"bits_to_copy={} current_buffer={} packet_count={} skip={} cross_packet={} "
"sample_rate={} stereo={}",
id(), packet_index, next_packet_index, packet_info.current_frame_size_,
static_cast<uint32_t>(bits_to_copy), static_cast<uint32_t>(data->current_buffer),
current_input_packet_count,
skip_count, last_cross_packet_copy_, GetSampleRate(data->sample_rate), bool(data->is_stereo));
}
const size_t packet_size = PreparePacket(packet_info.current_frame_size_, padding_start);
const xma::XmaDecodeRequest decode_request{
.packet_data = std::span<const uint8_t>(xma_frame_.data(), packet_size),
.sample_rate = static_cast<uint32_t>(GetSampleRate(data->sample_rate)),
.is_two_channel = bool(data->is_stereo),
};
const bool frame_decoded =
decoder_backend_ &&
decoder_backend_->DecodePacket(
decode_request, std::span<uint8_t>(raw_frame_.data(), raw_frame_.size()));
if (!frame_decoded && REXCVAR_GET(audio_xma_preserve_timeline)) {
// A frame the decoder rejects still occupies exactly kSamplesPerFrame
// samples of the stream's timeline, and the read offset advances past it
// below either way. Dropping the output entirely (the legacy behavior)
// silently shortens the stream by one frame - a latent correctness bug
// for multi-stream sources that must stay sample-locked, e.g. 5.1
// premixes carried as parallel stereo streams (AC6's cutscene mixes).
// Instrumented AC6 runs decode every cutscene frame successfully, so no
// in-game trigger is known - this is robustness against decode errors,
// not a fix for an audible symptom. Deliver the frame as silence
// instead - raw_frame_ is zero-filled above, so falling through emits
// one frame of silence in this stream (~10 ms, masked by the other
// streams) and the timeline holds.
REXAPU_DEBUG(
"XmaContext {}: undecodable frame (packet={} offset={} frame_bits={}) - "
"emitting silence to preserve the stream timeline",
id(), last_packet_index_, last_input_read_offset_before_,
packet_info.current_frame_size_);
}
if (frame_decoded || REXCVAR_GET(audio_xma_preserve_timeline)) {
current_frame_remaining_subframes_ = 4 << data->is_stereo;
last_decode_succeeded_ = frame_decoded;
if (is_loop_end_frame) {
loop_frame_output_limit_ = (data->loop_subframe_end + 1) << data->is_stereo;
} else {
loop_frame_output_limit_ = 0;
}
if (loop_start_skip_pending_) {
const uint8_t skip = data->loop_subframe_skip << data->is_stereo;
if (skip < current_frame_remaining_subframes_) {
current_frame_remaining_subframes_ -= skip;
}
loop_start_skip_pending_ = false;
}
}
if (!packet_info.isLastFrameInPacket()) {
const uint32_t next_frame_offset =
(data->input_buffer_read_offset + bits_to_copy) % kBitsPerPacket;
data->input_buffer_read_offset = (packet_index * kBitsPerPacket) + next_frame_offset;
last_input_read_offset_after_ = static_cast<uint32_t>(data->input_buffer_read_offset);
return;
}
const bool next_packet_in_next_buffer = next_packet_index >= current_input_packet_count;
uint32_t next_input_offset =
GetNextPacketReadOffset(data, next_packet_index, current_input_packet_count);
if (next_packet_in_next_buffer) {
last_swapped_input_buffer_ = true;
SwapInputBuffer(data);
} else if (next_input_offset == kBitsPerPacketHeader) {
last_swapped_input_buffer_ = true;
SwapInputBuffer(data);
if (data->IsAnyInputBufferValid()) {
next_input_offset =
xma::GetPacketFrameOffset(memory()->TranslatePhysical(data->GetCurrentInputBufferAddress()));
if (next_input_offset > kMaxFrameSizeinBits) {
log_decode_state("next-packet-frame-offset-invalid");
last_swapped_input_buffer_ = true;
SwapInputBuffer(data);
return;
}
}
}
data->input_buffer_read_offset = next_input_offset;
last_input_read_offset_after_ = static_cast<uint32_t>(data->input_buffer_read_offset);
}
} // namespace rex::audio