[XMA] Fix multiple audio issues in new decoder
- Handle XMA2 frames with headers split across packet boundaries (fixes audio cutoff in Capcom games) - Output buffer minimum space check using actual frame size instead of SDC-based formula, allowing tight-buffer contexts to decode (fixes PGR4 engine sounds) - Gate consume-only context clearing with tight-buffer heuristic to prevent disrupting playback while still allowing stream completion signaling (fixes Borderlands 2 dialog) - Clamp read offset 0 to packet header size to prevent decode rejection loop (fixes Dirt 2 crashes)
This commit is contained in:
@@ -154,6 +154,11 @@ struct XMA_CONTEXT_DATA {
|
||||
const bool IsConsumeOnlyContext() const {
|
||||
return (input_buffer_0_packet_count | input_buffer_1_packet_count) == 0;
|
||||
}
|
||||
// Whether the SDC-based minimum exceeds the output buffer size.
|
||||
const bool HasTightOutputBuffer() const {
|
||||
return (int32_t)((subframe_decode_count * 2) - 1) >
|
||||
(int32_t)output_buffer_block_count;
|
||||
}
|
||||
};
|
||||
static_assert_size(XMA_CONTEXT_DATA, 64);
|
||||
|
||||
|
||||
@@ -130,7 +130,16 @@ bool XmaContextNew::Work() {
|
||||
|
||||
if (data.IsConsumeOnlyContext()) {
|
||||
Consume(&output_rb, &data);
|
||||
if (data.output_buffer_read_offset == data.output_buffer_write_offset) {
|
||||
// Clearing contexts that match TightBufferOutput heuristic can disrupt
|
||||
// playback (e.g. audio noise during races in PGR4), so we only clear
|
||||
// contexts with enough output buffer headroom where empty input reliably
|
||||
// indicates the stream is finished (e.g. needed for dialog completion in
|
||||
// Borderlands 2 startup).
|
||||
// NOTE(has207): this feels wrong to have such a heuristic but it seems to
|
||||
// work for the known cases. May need to be revisited if failing cases are
|
||||
// found.
|
||||
if (!data.HasTightOutputBuffer() &&
|
||||
data.output_buffer_read_offset == data.output_buffer_write_offset) {
|
||||
ClearLocked(&data);
|
||||
}
|
||||
data.Store(context_ptr);
|
||||
@@ -138,7 +147,7 @@ bool XmaContextNew::Work() {
|
||||
}
|
||||
|
||||
const int32_t minimum_subframe_decode_count =
|
||||
(data.subframe_decode_count * 2) - 1;
|
||||
(kBytesPerFrameChannel / kOutputBytesPerBlock) << data.is_stereo;
|
||||
|
||||
// We don't have enough space to even make one pass
|
||||
// Waiting for decoder to return more space.
|
||||
@@ -337,6 +346,13 @@ void XmaContextNew::Decode(XMA_CONTEXT_DATA* data) {
|
||||
static_cast<void*>(current_input_buffer), data->output_buffer_ptr,
|
||||
data->output_buffer_block_count);
|
||||
|
||||
// Games like Dirt 2 can kick the decoder with read offset 0 (pointing into
|
||||
// the packet header) before filling in a valid offset. Clamp to the first
|
||||
// valid data position to avoid rejecting the packet entirely.
|
||||
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;
|
||||
@@ -360,10 +376,42 @@ void XmaContextNew::Decode(XMA_CONTEXT_DATA* data) {
|
||||
|
||||
const uint32_t relative_offset =
|
||||
data->input_buffer_read_offset % kBitsPerPacket;
|
||||
const kPacketInfo packet_info = GetPacketInfo(packet, relative_offset);
|
||||
kPacketInfo packet_info = GetPacketInfo(packet, relative_offset);
|
||||
const uint32_t packet_to_skip = xma::GetPacketSkipCount(packet) + 1;
|
||||
const uint32_t next_packet_index = packet_index + packet_to_skip;
|
||||
|
||||
// Frame header split across packet boundary — combine packets to read
|
||||
// the full 15-bit header and resolve the real frame size.
|
||||
// Only detected for XMA2 packets where the header provides an authoritative
|
||||
// frame count. XMA1 packets lack a frame count field so split headers
|
||||
// cannot be detected — if XMA1 encoders can produce them, those frames
|
||||
// will still be silently lost.
|
||||
if (packet_info.current_frame_size_ == 0) {
|
||||
const uint8_t* next_packet =
|
||||
GetNextPacket(data, next_packet_index, current_input_packet_count);
|
||||
if (!next_packet) {
|
||||
// Matching split-body error handling below; correct error code unknown.
|
||||
data->error_status = 4;
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
// Matching split-body error handling below; correct error code unknown.
|
||||
data->error_status = 4;
|
||||
return;
|
||||
}
|
||||
packet_info.current_frame_size_ = (uint32_t)frame_size;
|
||||
}
|
||||
|
||||
BitStream stream =
|
||||
BitStream(current_input_buffer, (packet_index + 1) * kBitsPerPacket);
|
||||
stream.SetOffset(data->input_buffer_read_offset);
|
||||
@@ -623,7 +671,15 @@ const kPacketInfo XmaContextNew::GetPacketInfo(uint8_t* packet,
|
||||
|
||||
if (xma::IsPacketXma2Type(packet)) {
|
||||
const uint8_t xma2_frame_count = xma::GetPacketFrameCount(packet);
|
||||
if (xma2_frame_count != packet_info.frame_count_) {
|
||||
if (xma2_frame_count > packet_info.frame_count_) {
|
||||
// Frame header split across packet boundary — scanner couldn't
|
||||
// peek the full 15-bit header. Trust the XMA2 header count.
|
||||
if (packet_info.current_frame_size_ == 0) {
|
||||
// Current frame is the split-header frame
|
||||
packet_info.current_frame_ = packet_info.frame_count_;
|
||||
}
|
||||
packet_info.frame_count_ = xma2_frame_count;
|
||||
} else if (xma2_frame_count != packet_info.frame_count_) {
|
||||
XELOGE(
|
||||
"XmaContext {}: XMA2 packet header defines different amount of "
|
||||
"frames than internally found! (Header: {} Found: {})",
|
||||
|
||||
Reference in New Issue
Block a user