diff --git a/src/xenia/apu/xma_context.h b/src/xenia/apu/xma_context.h index f4e9645a6..9d8a4e847 100644 --- a/src/xenia/apu/xma_context.h +++ b/src/xenia/apu/xma_context.h @@ -68,11 +68,15 @@ struct XMA_CONTEXT_DATA { uint32_t loop_subframe_skip : 3; // +17bit, XMASetLoopData might be // subframe_decode_count uint32_t subframe_decode_count : 4; // +20bit - uint32_t subframe_skip_count : 3; // +24bit - uint32_t sample_rate : 2; // +27bit enum of sample rates - uint32_t is_stereo : 1; // +29bit - uint32_t unk_dword_1_c : 1; // +30bit - uint32_t output_buffer_valid : 1; // +31bit, XMAIsOutputBufferValid + uint32_t output_buffer_padding : 3; // +24bit, extra output buffer blocks + // reserved per decoded frame + // NOTE(has207): this is pure guess + // but that's how we're using it + // currently + uint32_t sample_rate : 2; // +27bit enum of sample rates + uint32_t is_stereo : 1; // +29bit + uint32_t unk_dword_1_c : 1; // +30bit + uint32_t output_buffer_valid : 1; // +31bit, XMAIsOutputBufferValid // DWORD 2 uint32_t input_buffer_read_offset : 26; // XMAGetInputBufferReadOffset diff --git a/src/xenia/apu/xma_context_new.cc b/src/xenia/apu/xma_context_new.cc index 46f4d8919..56fdef261 100644 --- a/src/xenia/apu/xma_context_new.cc +++ b/src/xenia/apu/xma_context_new.cc @@ -131,14 +131,6 @@ bool XmaContextNew::Work() { if (data.IsConsumeOnlyContext()) { Consume(&output_rb, &data); - // 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); @@ -148,7 +140,8 @@ bool XmaContextNew::Work() { } const int32_t minimum_subframe_decode_count = - (kBytesPerFrameChannel / kOutputBytesPerBlock) << data.is_stereo; + ((kBytesPerFrameChannel / kOutputBytesPerBlock) << data.is_stereo) + + data.output_buffer_padding; // We don't have enough space to even make one pass // Waiting for decoder to return more space. @@ -165,12 +158,12 @@ bool XmaContextNew::Work() { minimum_subframe_decode_count) { XELOGAPU( "XmaContext {}: Write Count: {}, Capacity: {} - {} {} Subframes: {} " - "Skip: {}", + "Padding: {}", id(), (uint32_t)output_rb.write_count(), remaining_subframe_blocks_in_output_buffer_, data.input_buffer_0_valid + (data.input_buffer_1_valid << 1), data.output_buffer_valid, data.subframe_decode_count, - data.subframe_skip_count); + data.output_buffer_padding); Decode(&data); Consume(&output_rb, &data); @@ -273,12 +266,19 @@ void XmaContextNew::Consume(RingBuffer* XE_RESTRICT output_rb, const int8_t raw_frame_read_offset = ((kBytesPerFrameChannel / kOutputBytesPerBlock) << data->is_stereo) - current_frame_remaining_subframes_; - // + data->subframe_skip_count; output_rb->Write( raw_frame_.data() + (kOutputBytesPerBlock * raw_frame_read_offset), subframes_to_write * kOutputBytesPerBlock); - remaining_subframe_blocks_in_output_buffer_ -= subframes_to_write; + + // Reserve extra blocks as headroom when unk_skip_decode is set. + // Only apply when the frame is fully consumed to avoid double-counting. + 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; XELOGAPU("XmaContext {}: Consume: {} - {} - {} - {} - {}", id(),